Ram*_*dar 23 ruby activerecord ruby-on-rails
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
Run Code Online (Sandbox Code Playgroud)
现在我们可以致电:
Project.first.tasks.initial_tasks # how it works
Run Code Online (Sandbox Code Playgroud)
initial_tasks 是一个类方法,我们不能在对象上调用类方法.
Project.first.tasks返回一个ActiveRecord :: Relation对象,那怎么能调用initial_tasks呢?
请解释.
zea*_*soi 32
关于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
Run Code Online (Sandbox Code Playgroud)
首先,调用作用域返回一个ActiveRecord::Relation对象:
Article.published.class
#=> ActiveRecord::Relation
Article.featured.class
#=> ActiveRecord::Relation
Run Code Online (Sandbox Code Playgroud)
然后,您可以ActiveRecord::Relation使用相应模型的类方法对对象进行操作:
Article.published.featured.latest_article
Article.featured.titles
Run Code Online (Sandbox Code Playgroud)
这是理解类方法之间关系的一种迂回方式ActiveRecord::Relation,但要点是:
ActiveRecord::Relation对象ActiveRecord::Relation对象可以访问类方法Nik*_*kov 27
它非常容易探索.你这样做:
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
Run Code Online (Sandbox Code Playgroud)
然后打电话给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'",
Run Code Online (Sandbox Code Playgroud)
这就是你所需要的一切.易于探索,但不是那么容易理解.
现在我将解释这意味着什么.当你调用Project#tasks方法时,它不会返回ActiveRecord :: Relation对象.实际上它会返回一个运行时创建的类的实例,该类名为Task :: ActiveRecord_Associations_CollectionProxy,它继承自ActiveRecord :: Associations :: CollextionProxy,后者继承自ActiveRecord :: Relation.此运行时创建的类与Task类链接,并包含动态定义的(通过method_missing)代理方法,这些方法委托对Task类方法的调用,并将关联范围与类级方法返回的类定义范围合并.
它是如何工作的(真的非平凡):
DelegateCache具有每次继承ActiveRecord :: Base时DelegateCache.inherited定义@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
Run Code Online (Sandbox Code Playgroud)
在这里,这些类得到了Task::ActiveRecord_Associations_CollectionProxy
前面提到的不寻常的名字.
#initial_tasks上Project.tasks).在这样的调用中,它动态地定义了委托给类级方法的新的运行时类实例方法.现在,您将Task类链接到Task :: ActiveRecord_Associations_CollectionProxy类,其中包含代理对Task类级方法的调用的所有实例级方法,获取范围结果并将其与当前关联范围(此处)合并.这就是AR在运行时创建的类上使用动态定义的方法而不是在ActiveRecord :: Relation上使用低效的method_missing调用.
如果你不理解所有这些东西,我认为没关系.只需在关联上调用类级别方法:)