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
之间关系的一种迂回方式,但要点是:
- 根据定义 ,模型范围返回
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 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类方法的调用,并将关联范围与类级方法返回的类定义范围合并。
它是如何工作的(真的非平凡):
- 有ActiveRecord :: Base类。 这里由ActiveRecord :: Delegation :: DelegateCache模块扩展的类对象。
-
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
前面提到过。 - 好的,现在我们的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是数组或单个记录,它可以访问类方法。