ActiveRecord :: Relation对象如何调用类方法

时间:2014-02-10 15:25:10

标签: ruby-on-rails ruby activerecord

ActiveRecord :: Relation对象如何调用类方法?

class Project < ActiveRecord::Base
  has_many :tasks
end

class Task < ActiveRecord::Base
  belongs_to :project

  def self.initial_tasks # class methods
   # here return initial tasks
  end
end

现在我们可以致电:

Project.first.tasks.initial_tasks # how it works

initial_tasks是一个类方法,我们不能在对象上调用类方法。

Project.first.tasks返回一个ActiveRecord :: Relation对象,那么它怎么能够调用initial_tasks

请解释。

4 个答案:

答案 0 :(得分:32)

关于ActiveRecord::Relation对象的类方法的应用程序没有太多文档,但我们可以通过查看ActiveRecord scopes的工作原理来理解这种行为。

首先,Rails模型范围将返回ActiveRecord::Relation对象。来自文档:

  

模型上的类方法可自动在范围内使用。假设以下设置:

class Article < ActiveRecord::Base
  scope :published, -> { where(published: true) }
  scope :featured, -> { where(featured: true) }

  def self.latest_article
    order('published_at desc').first
  end

  def self.titles
    pluck(:title)
  end
end

首先,调用范围会返回ActiveRecord::Relation对象:

Article.published.class
#=> ActiveRecord::Relation

Article.featured.class
#=> ActiveRecord::Relation

然后,您可以使用相应模型的类方法对ActiveRecord::Relation对象进行操作:

Article.published.featured.latest_article
Article.featured.titles

这是理解类方法与ActiveRecord::Relation之间关系的一种迂回方式,但要点是:

  1. 根据定义,模型范围返回ActiveRecord::Relation个对象
  2. 根据定义,范围可以访问类方法
  3. 因此 ActiveRecord::Relation个对象可以访问类方法

答案 1 :(得分:24)

它非常容易探索。你这样做了:

class Project < ActiveRecord::Base
  has_many :tasks
end

class Task < ActiveRecord::Base
  belongs_to :project

  def self.initial_tasks  #class methods
    1 / 0
  end
end

然后致电Project.first.tasks.initial_tasks,您就会得到:

  Division by zero
  ...
  .../gems/activerecord-4.1.0/lib/active_record/relation/delegation.rb:70:in `block in re
  .../gems/activerecord-4.1.0/lib/active_record/associations/collection_proxy.rb:872:in `
  .../gems/activerecord-4.1.0/lib/active_record/relation.rb:286:in `scoping'",
  .../gems/activerecord-4.1.0/lib/active_record/associations/collection_proxy.rb:872:in `
  .../gems/activerecord-4.1.0/lib/active_record/relation/delegation.rb:70:in `initial_tasks'",

基本上你需要的就是这些。易于探索但不太容易理解。

现在我将解释这意味着什么。 当您调用Project#tasks方法时,不会返回您的ActiveRecord :: Relation对象。实际上它会返回一个运行时创建的类的实例,该类名为Task :: ActiveRecord_Associations_CollectionProxy,它继承自ActiveRecord :: Associations :: CollextionProxy,后者继承自ActiveRecord :: Relation。这个运行时创建的类与Task类链接,并包含动态定义的(通过method_missing)代理方法,这些方法委托对Task类方法的调用,并将关联范围与类级方法返回的类定义范围合并。

它如何运作(真的非平凡):

  1. 有ActiveRecord :: Base类。类对象扩展 ActiveRecord::Delegation::DelegateCache模块here
  2. 每次继承ActiveRecord :: Base时,DelegateCache都有DelegateCache.inherited回调定义@relation_delegate_cache属性。这意味着所有AR :: Base后代类都将具有此类属性。回调calls DelegateCache#initialize_relation_delegate_cache方法,用于使用运行时创建的类填充缓存属性:

    [
      ActiveRecord::Relation,
      ActiveRecord::Associations::CollectionProxy,
      ActiveRecord::AssociationRelation
    ].each do |klass|
      delegate = Class.new(klass) {
        include ClassSpecificRelation
      }
      const_set klass.name.gsub('::', '_'), delegate
      cache[klass] = delegate
    end
    

    这些类在这里得到了不寻常的名字a-la Task::ActiveRecord_Associations_CollectionProxy 前面提到过。

  3. 好的,现在我们的Task模型有到运行时定义的关系类的链接。这些类有一些称为ClassSpecificRelation(包括here)的问题。关注点添加了method_missing方法,该方法检测对关系对象的类方法调用(例如在#initial_tasks上调用Project.tasks)。在此类调用它dynamically defines新的运行时类实例方法,它们委托给类级方法。现在,您将Task类链接到Task :: ActiveRecord_Associations_CollectionProxy类,其中包含代理对Task类级方法的调用的所有实例级方法,获取范围结果并将其与当前关联范围(here)合并。
  4. 这是AR如何在运行时创建的类上使用动态定义的方法而不是在ActiveRecord :: Relation上使用低效的method_missing调用。

    如果您不理解所有这些内容,我认为没问题。只需在关联上调用类级别方法:)

答案 2 :(得分:0)

在ActiveRecord :: Relation中,Relation表示整个表 你的班级邮寄地图是表格,

所以ActiveRecord :: Relation是数组或单个记录,它可以访问类方法。

答案 3 :(得分:0)

对于在实际访问类方法内的记录筛选列表中遇到麻烦的任何人,您只需调用all即可返回所需的列表,而不是该表中所有通过{访问的记录{1}}。