【问题标题】:Retrieve all association's attributes of an AR model?检索 AR 模型的所有关联属性?
【发布时间】:2017-11-25 18:02:49
【问题描述】:

您认为检索 AR 模型所有关联的所有属性的最佳方式是什么?

即:假设我们有模型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

我想通过调用 Target 实例上的方法来检索所有关联的属性:

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 => { ... }, ....
   }

目前我通过迭代每个关联,然后对关联的每个模型进行迭代来完成这项工作,但这有点贵,你认为我可以如何优化这个?

请注意:我意识到你不能在 has_many 与 nested_attributes 的关联上调用 target.countries_attributes,one_to_one 关联允许调用 target.country_attributes

【问题讨论】:

    标签: ruby-on-rails activerecord


    【解决方案1】:

    我不清楚您对所有关联进行迭代是什么意思。你已经在使用反射了吗?

    仍然好奇是否有更简洁的方法,但这是我能想到的,或多或少会导致您在示例中显示的哈希:

    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
    

    这是一个通过script/console 进行的 irb 会话,以展示它是如何工作的。首先,一些环境:

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

    这是新方法的输出:

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

    【讨论】:

    • 是的,我使用的是反射,但这种方法更优化,只有一件事,我从object_or_array = me.send(association_name).all 中删除了all 这不是真的必要,如果有one_to_one 关系,就会搞砸事情.谢谢!
    【解决方案2】:

    用异常处理试试这个:

    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
    

    【讨论】:

      【解决方案3】:

      这是 Stéphan Kochen 的 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
      

      【讨论】:

        猜你喜欢
        • 2011-12-29
        • 1970-01-01
        • 1970-01-01
        • 2013-07-24
        • 2019-11-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多