开发者

Retrieve all association's attributes of an AR model?

开发者 https://www.devze.com 2022-12-20 13:56 出处:网络
What do you think is the most optimal way to retrieve all attributes for all the associations an AR model has?

What do you think is the most optimal way to retrieve all attributes for all the associations an AR model has?

i.e: let's say we have the model Target.

开发者_Go百科
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


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}}}


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


This is updated version of Stéphan Kochen's 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
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号