0

在 Rails 3.2 模型中,希望创建一个“to_csv”类方法来从 ActiveRecord 范围生成 CSV 输出,如下所示:

class Post
   # ...
   def self.to_csv(options = {})
     CSV.generate(options) do |csv|
       scoped.each {|post| csv << record.attributes.values_at('title', 'text')}
     end
   end
end

我希望我可以像这样使用它:

User.first.posts.to_csv

但是,该方法似乎被 Array #to_csv 方法覆盖。即使我这样做:

User.first.posts.scoped.to_csv

...并且来自User.first.posts.scoped的结果明确是一个 ActiveRecord::Relation,我仍然点击了 Array 方法。如果我将该方法重命名为“to_csvx”之类的名称,它会按预期工作。

我希望有人可以解释 ActiveRecord::Relation 对象如何/为什么优先命中 Array #to_csv 方法而不是 to_csv 类方法。虽然这(一个期望被 AR 范围调用的类方法)似乎是合理的,但我想知道整个想法是否存在固有问题?

4

1 回答 1

4

好的,回答我自己的问题超级蹩脚,但基于@m_x 和 Railscast 239 http://railscasts.com/episodes/239-activerecord-relation-walkthrough的评论我更深入地了解 Rails 源代码......我可以看到 Relationrespond_to尊重并method_missing更喜欢 Array 方法,而不是任何其他未为 Relation 明确定义的方法。

# from rails/active_record/relation/delegation.rb

def respond_to?(method, include_private = false)
  super || Array.method_defined?(method) ||
    @klass.respond_to?(method, include_private) ||
    arel.respond_to?(method, include_private)
end

protected

def method_missing(method, *args, &block)
  if Array.method_defined?(method)
    ::ActiveRecord::Delegation.delegate method, :to => :to_a
    to_a.send(method, *args, &block)
  elsif @klass.respond_to?(method)
    ::ActiveRecord::Delegation.delegate_to_scoped_klass(method)
    scoping { @klass.send(method, *args, &block) }
  elsif arel.respond_to?(method)
    ::ActiveRecord::Delegation.delegate method, :to => :arel
    arel.send(method, *args, &block)
  else
    super
  end
end

因此,如果您将 ActiveRecord 的“作用域”类方法命名为与 Array 实例方法相同,则 Relation 将转换为 Array,并且将调用 Array 方法而不是您的类方法。

简单的解决方案:只需选择另一个方法名称。

于 2013-04-15T21:25:04.177 回答