Sunday, June 25, 2017

Retrieve all association's attributes of an AR model?

Leave a Comment

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 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment