Get all the association attributes of an AR model?

What do you think is the best way to get all the attributes for all the associations that the AR model has?

ie: let's say we have a Target model.

 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 would like to get all the association attributes by calling the method on the 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 => { ... }, .... } 

I am currently doing this work, iterating through each association, and then through each association model, but it is somehow expensive. Do you think I can optimize this?

Just a note: I realized that you cannot name target.countries_attributes in has_many associations with associations nested_attributes , one_to_one to call target.country_attributes

+7
ruby-on-rails activerecord
source share
3 answers

I do not understand what you mean when repeating all the associations. Are you already using reflections?

It is still interesting if there is a more neat way, but this is what I could come up with, which more or less leads to the hash that you show 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 is the irb session via 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 is the conclusion of the new method:

 >> t.associations_attributes => {:tags=>{1=>{"id"=>1, "name"=>"blueish", "target_id"=>1}, 2=>{"id"=>2, "name"=>"friendly", "target_id"=>1}}} 
+16
source share

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 
+1
source share

This is an updated version of Stéphan Kochen code for Rails 4.2

 def associations_attributes association_names = self.class.reflect_on_all_associations.collect { |r| r.name } me = self.class.includes(association_names).find self.id pairs = association_names.collect do |association_name| object_or_array = me.send(association_name) if object_or_array.is_a? ActiveRecord::Associations::CollectionProxy association_pairs = object_or_array.collect { |o| [o.id, o.attributes] } [association_name, Hash[*association_pairs.flatten(1)]] else [association_name, object_or_array.attributes] end end Hash[*pairs.flatten(1)] end 
+1
source share

All Articles