Best way to declare a rails array field? - mysql

What is the best way/practice to declare a Rails(4) array field (with mysql database)? I need to store some ids into that array. I tried to do this using the ActiveRecord Serializer and I customized the attribute accessors so my field can behave like an array.
class OfficeIds < ActiveRecord::Base
serialize :office_ids
def office_ids=(ids)
ids = ids.join(",") if ids.is_a?(Array)
write_attribute(:office_ids, ids)
end
def office_ids
(read_attribute(:office_ids) || "").split(",")
end
end
I feel that this is not the best approach for this kind of situation. Any help would be appreciated. Thanks!

If you're using the serializer, there's no need to write a wrapper method for this. You should be able to assign arbitrary objects to that field:
ids = OfficeIds.new
ids.office_ids = [ 1, 2, 3 ]
ids.save
It is rather odd to have a model called OfficeIds though, as a plural name for this willc cause all kinds of trouble. Are you sure you don't want a traditional has_many relationship for these?

Related

ActiveModelSerializers Polymorphic Json

Been wrestling with trying to get polymorphic serializers working and testing data via rspec. Just upgraded to 0.10+
I found this post, which makes a lot of sense, and does give me a entry into generating the serializations, however, when doing it for polymorphs, I never get the type and id properly named (expecting to see asset_id and asset_type nested)
{:id=>1,
:label=>"Today I feel amazing!",
:position=>0,
:status=>"active",
:media_container_id=>1,
:asset=>
{:id=>4
Test ActiveModel::Serializer classes with Rspec
class MediaSerializer < ApplicationSerializer
attributes :id,
:label,
has_one :asset, polymorphic: true
end
I noticed that the tests dont even seem to properly add the polymorphic identifiers either (ie asset_id, asset_type -- or in the test case imageable_id, imageable_type)
https://github.com/rails-api/active_model_serializers/commit/045fa9bc072a04f5a94d23f3d955e49bdaba74a1#diff-c3565d7d6d40da1b2bf75e13eb8e6afbR36
If I go straight up MediaSerialzer.new(media) I can poke at the .associations, but I cant seem to get them to render as if I was generating a full payload
From the docs
https://github.com/rails-api/active_model_serializers
serializer_options = {}
serializer = SomeSerializer.new(resource, serializer_options)
serializer.attributes
serializer.associations
Im pretty sure Im missing something/doing something wrong - any guidance would be great.
Thanks
It isn't easy to get the effect you are looking for, but it is possible.
You can access the hash generated by the serializer by overriding the associations method.
class MediaSerializer < ApplicationSerializer
attributes :id,
:label,
has_one :asset, polymorphic: true
def associations details
data = super
data[:asset] = relabel_asset(data[:asset])
data
end
def relabel_asset asset
labelled_asset = {}
asset.keys.each do |k|
labelled_asset["asset_#{k}"] = asset[k];
end
labelled_asset
end
end
I learnt alot about ActiveModelSerializer to get the hang of this! I referred to Ryan Bates' podcast on the topic:
http://railscasts.com/episodes/409-active-model-serializers
In there he describes how you can override the attributes method and call super to get access to the hash generated by the serializer. I guessed I could do the same trick for the associations method mentioned in your post. From there it takes a little bit of Ruby to replace all the keys, but, if I have understood correctly what you require, it is technically possible.
Hope that helps!

Get rails ActiveRecord instead of Array of objects for multiple queries

I have a table that stores queries that return a list of users.
I then have a method "get_public" to a "Banana" model that execute multiple queries using logic AND between them.
So, when I do
Banana.find(x).get_public I receive an Array of users (the ones suitable to that banana object).
The get_public method is like this:
def get_public
pb = []
banana_queries.each do |q|
pb << User.find_by_sql(q.query)
end
pb.inject(:'&')
end
But, would be great if I could get ActiveRecord::Relation instead. I want to do something like this after: Banana.find(x).get_public.where(...)
Any way to modify get_public and achieve this?
I am not sure I correcly undestood the problem, but I will try to help anyway.
As especified here
where returns an ActiveRecord::Relation
find (and its related dynamic methods) returns a single model object
So I suggest divide your queries into: 'joins' and 'where' fields. Your new code should look like something like this:
pb << User.joins(q.query_joins).where(q.query_where)
Also find methods will are deprecated in rails 4, so using where is recommended.
Hope I haven't missed the point too much :-)

Ember.js with Rails accepts_nested_attributes_for and polymorphic has_many/belongs_to

On the Rails API side I have the following 2 models:
class Grower < ActiveRecord::Base
has_many :addresses, as: :addressable
accepts_nested_attributes_for :addresses
end
class Address < ActiveRecord::Base
belongs_to :addressable, polymorphic: true
end
as well as a Growers controller which returns and can create/update Growers with embedded Addresses attributes. I also have an Addresses controller with proper routing so that Addresses can be viewed/created/updated for a specific Grower. The latter is more of an "in-case", and I'm not sure I'll be needing to return/update/create addresses as a separate payload.
I am starting to try and piece together an Ember app that would allow me to view/edit/create a Grower at the same time as its Address(es). Can anyone point me to an existing real or sample app that does this? I will be posting my code as I go along, but I already have an idea of some areas where I will be running into trouble:
Rails returns/expects nested params named addresses_attributes. Ember, I am sure, doesn't use that convention. What's the best approach of resolving this?
Because of the polymorphic association (objects other than Grower can be addressable), on the API/Address side, to get the correct belongs_to object, Rails uses addressable_id in conjunction with addressable_type. In this example the addressable_type would be "Grower", and the addressable_id would be the grower_id value. How would one go about translating that on the Ember side?
UPDATE:
I got it to work at least a couple different ways. My preferred solution, at least for this particular case, is in the answer section.
Here is a sample of code based #yuяi's strategy that worked well for me:
App.Post = DS.Model.extend
comments: DS.hasMany('comment')
App.PostSerializer = DS.ActiveModelSerializer.extend( DS.EmbeddedRecordsMixin,
attrs:
comments: {embedded: 'always'}
keyForAttribute: (attr) ->
if attr == "comments"
"comments_attributes"
else
#_super(attr)
)
This solution worked well with Ember 1.6.1 and Ember Data 1.0.0-beta.8.2a68c63a.
I found a couple ways to get it done, but the final approach doesn't require any changes on the Rails/API side.
On the client (Ember) side:
I added the addresses hasMany property to the App.Grower model. I also mapped it on the RESTAdapter to what's expected from the API, by setting the key for addresses to addresses_attributes.
I added the grower (for now - will change to addressable once I have other addressable models) belongsTo property on App.Address. It's not really required for what I'm doing, but it might be useful in the future.
I set the addresses on the RESTAdapter to be embedded: 'always'.
On the App.GrowersEditController, I just do a model.save (transaction.commit), and the child addresses are automatically saved via the API call.
On the App.GrowersAddController, I use the App.Address.createRecord and App.Grower.createRecord methods using the user-entered Grower and Address data. Then I use the pushObject method to append the Address to the Grower, and then call a save on the Grower (commit on transaction). Again, the address data gets submitted and saved automatically.

rails alternative to enum and view integration

rails doesn't offer ENUM types, but I do need a data member which can accept only five values. Moreover, I want it to be integrated automatically with the Rails Forms Helper: select_tag.
What's the right solution to my situation?
P.S, I'd rather not to use external plugins, if built-in and neat solution exist.
I keep functionality like this as close to where it's used as possible.
If the values are used by a single model, just keep them in the model, e.g., if users have certain possible types, and only those types, it might look something like:
class User < ActiveRecord::Base
TYPES = %w{guest, paid, admin}
# Plus validation on the `type` field.
# Maybe plus a setter override that also validates.
end
When you need to refer to those types elsewhere, like as allowable values in a select:
User::TYPES
There are a number of valuable tweaks around this, like providing decorators to make them "human readable" (capitalized, spaced, whatever) or metaprogramming methods to allow things like:
user.is_guest? # Or...
user.make_guest! # Or...
user.guest!
I use my own small gem for this functionality because it's often the case that a full-blown association is just too much and provides no value. It allows things like:
class User < ActiveRecord::Base
simple_enum :user_type, %w{guest, paid, admin}
end
Using the tip from this blog post, which offers a very simple approach.
You can set in on your model and then use it on your controller or views.
In this case it will map the status with integers.
STATUS = { pending: 0, active: 1, inactive: 2, deleted: 3 }
def status
STATUS.key(read_attribute(:status))
end
def status=(s)
write_attribute(:status, STATUS[s])
end
Rails 4.1 has enums. I just upgraded to the beta and it's working like a charm!
http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html
I tried active_enum gem, which is great, but it's not compatible with rails 4. The solution from Paulo works pretty well and you can extract the enum into a concern if you want, but it just started getting too heavy for me so I rather upgraded!
You can easily define the Enum as a helper in ApplicationHelper
class ApplicationHelper
def select_range
%w{"a", "b", "c", "d", "e"}
end
end
Then in view you can call select_range freely.

Overwriting all default accessors in a rails model

class Song < ActiveRecord::Base
# Uses an integer of seconds to hold the length of the song
def length=(minutes)
write_attribute(:length, minutes.to_i * 60)
end
def length
read_attribute(:length) / 60
end
end
This is an easy example by rails api doc.
Is it possible overwrite all attributes for a model without overwrite each one?
Do you look for something like that? Don't know why you would want to do it, but here you go :)
class Song < ActiveRecord::Base
self.columns_hash.keys.each do |name|
define_method :"#{name}=" do
# set
end
define_method :"#{name}" do
# get
end
# OR
class_eval(<<-METHOD, __FILE__, __LINE__ + 1)
def #{name}=
# set
end
def #{name}
# get
end
METHOD
end
end
I'm not sure of a use case where this would be a good idea. However, all rails models dynamically have their properties assigned to them (assuming it isn't already in the class). The answer is partially in your question.
You can override the read_attribute() and write_attribute() methods. That would apply your transformations to every attribute whether they were written to by the accessor or populated in bulk in the controller. Just be careful to not mutate important attributes like the 'id' attribute.
Ruby has a shortcut that is used in rails code a fair bit that can help you. It's the %w keyword. %w will create an array of words based on the symbols inside the parentheses. Because it's an array you can do useful things like this:
#excludes = %w(id, name)
def read_attribute name
value = super
if(not #excludes.member? name)
value = value.to_i * 60
end
value
end
def write_attribute name, value
if(not #excludes.member? name)
value = value.to_i / 60
end
super
end
That should get you started. There are more advanced constructs like using lambdas, etc. Keep in mind you should write some thorough unit tests to make sure you don't have any unintended consequences. You may have to include more attribute names in the list of excludes.
edit: (read|write)_attributes -> (read|write)_attribute