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

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呢?

请解释。

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

首先,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 

首先,调用scope返回一个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对象可以访问类方法

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

 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,它inheritance自ActiveRecord :: Associations :: CollextionProxy,后者inheritance自ActiveRecord :: Relation。 此运行时创建的类与Task类链接,并包含动态定义的(通过method_missing)代理方法,这些方法委派对Task类方法的调用,并将关联范围与类级方法返回的类定义范围合并。

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

  1. 有ActiveRecord :: Base类。 这里由ActiveRecord :: Delegation :: DelegateCache模块扩展的类对象。
  2. DelegateCache具有DelegateCache.inherited回调,每次inheritanceActiveRecord :: Base时都会定义@relation_delegate_cache属性。 这意味着所有AR :: Base后代类都将具有此属性。 回调调用 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的问题(包括在这里 )。 关注点添加了method_missing方法,该方法检测关系对象上的类方法调用(例如在Project.tasks上调用#initial_tasks )。 在这样的调用中,它动态地定义了委托给类级方法的新的运行时类实例方法。 现在,您将Task类链接到Task :: ActiveRecord_Associations_CollectionProxy类,其中包含代理对Task类级方法的调用的所有实例级方法,获取范围结果并将其与当前关联范围( 此处 )合并。

这就是AR在运行时创建的类上更喜欢动态定义的方法,而不是在ActiveRecord :: Relation上使用低效的method_missing调用。

如果你不理解所有这些东西,我认为没关系。 只需在关联上调用类级别方法:)

在ActiveRecord :: Relation中,Relation代表整个表,你的类Post是映射表,

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