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

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,但要点是:

  1. 根据定义,模型范围返回ActiveRecord::Relation对象
  2. 根据定义,范围可以访问类方法
  3. 因此,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类方法的调用,并将关联范围与类级方法返回的类定义范围合并.

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

  1. 有ActiveRecord :: Base类.这里ActiveRecord :: Delegation :: DelegateCache模块扩展的类对象 .
  2. 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 前面提到的不寻常的名字.

  3. 好的,现在我们的Task模型有到运行时定义的关系类的链接.这些类有一些称为ClassSpecificRelation(包含在这里)的问题.令人担忧的增加了method_missing的检测类,方法调用的关系对象(例如调用方法#initial_tasksProject.tasks).在这样的调用中,它动态地定义了委托给类级方法的新的运行时类实例方法.现在,您将Task类链接到Task :: ActiveRecord_Associations_CollectionProxy类,其中包含代理对Task类级方法的调用的所有实例级方法,获取范围结果并将其与当前关联范围(此处)合并.

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

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