How do you think is the more optimum way to retrieve all the attributes for each association that an AR model has?
i.e: let's say we have the model Target
.
class Target < ActiveRecord::Base has_many :countries has_many :cities has_many :towns has_many :colleges has_many :tags accepts_nested_attributes_for :countries, :cities, ... end
I'd like to retrieve all the association's attributes by calling a method on a Target instance:
target.associations_attributes >> { :countries => { "1" => { :name => "United States", :code => "US", :id => 1 }, "2" => { :name => "Canada", :code => "CA", :id => 2 } }, :cities => { "1" => { :name => "New York", :region_id => 1, :id => 1 } }, :regions => { ... }, :colleges => { ... }, .... }
Currently I make this work by iterating on each association, and then on each model of the association, But it's kind of expensive, How do you think I can optimize this?
Just a note: I realized you can't call target.countries_attributes
on has_many
associations with nested_attributes
, one_to_one
associations allow to call target.country_attributes
2 Answers
Answers 1
I'm not clear on what you mean with iterating on all associations. Are you already using reflections?
Still curious if there's a neater way, but this is what I could come up with, which more or less results in the hash you're showing in your example:
class Target < ActiveRecord::Base has_many :tags def associations_attributes # Get a list of symbols of the association names in this class association_names = self.class.reflect_on_all_associations.collect { |r| r.name } # Fetch myself again, but include all associations me = self.class.find self.id, :include => association_names # Collect an array of pairs, which we can use to build the hash we want pairs = association_names.collect do |association_name| # Get the association object(s) object_or_array = me.send(association_name) # Build the single pair for this association if object_or_array.is_a? Array # If this is a has_many or the like, use the same array-of-pairs trick # to build a hash of "id => attributes" association_pairs = object_or_array.collect { |o| [o.id, o.attributes] } [association_name, Hash[*association_pairs.flatten(1)]] else # has_one, belongs_to, etc. [association_name, object_or_array.attributes] end end # Build the final hash Hash[*pairs.flatten(1)] end end
And here's an irb session through script/console
to show how it works. First, some environment:
>> t = Target.create! :name => 'foobar' => #<Target id: 1, name: "foobar"> >> t.tags.create! :name => 'blueish' => #<Tag id: 1, name: "blueish", target_id: 1> >> t.tags.create! :name => 'friendly' => #<Tag id: 2, name: "friendly", target_id: 1> >> t.tags => [#<Tag id: 1, name: "blueish", target_id: 1>, #<Tag id: 2, name: "friendly", target_id: 1>]
And here's the output from the new method:
>> t.associations_attributes => {:tags=>{1=>{"id"=>1, "name"=>"blueish", "target_id"=>1}, 2=>{"id"=>2, "name"=>"friendly", "target_id"=>1}}}
Answers 2
try this with exception handling:
class Target < ActiveRecord::Base def associations_attributes tmp = {} self.class.reflections.symbolize_keys.keys.each do |key| begin data = self.send(key) || {} if data.is_a?(ActiveRecord::Base) tmp[key] = data.attributes.symbolize_keys! else mapped_data = data.map { |item| item.attributes.symbolize_keys! } tmp[key] = mapped_data.each_with_index.to_h.invert end rescue Exception => e tmp[key] = e.message end end tmp end end
0 comments:
Post a Comment