通过对象实例调用 class 方法

Calling a class method through an object instance

Railscasts #4 使用此示例代码:

class Task < ActiveRecord::Base
  belongs_to :project

  def self.find_incomplete
    find_all_by_complete(:false, :order => "created_at DESC")
  end
end

class ProjectsController < ApplicationController
  def show
    @project = Project.find(params[:id])
    @tasks = @project.tasks.find_incomplete
  end
end

使用 @project.tasks.find_incomplete,仅查找属于该特定 Project 实例的不完整订单。

我希望该调用等同于 Task.find_incomplete,但事实并非如此。这个怎么可能? Rails(或 Ruby)现在如何为那个 Project 实例中的那些特定 Task 调用该方法?

之所以可行,是因为合并了 ActiveRecord 关系的范围。这并不是说 find_incomplete 是 运行 单个任务实例。

@project.tasks 为该项目实例创建任务的 ActiveRecord 范围,然后当您的 find_incomplete 方法被调用时该范围仍然有效。

查看此处的文档:http://guides.rubyonrails.org/active_record_querying.html#scopes

您的 find_incomplete 方法与文档中 self.published 示例的工作方式相同。

想想下面的 SQL 查询 运行:

@project.tasks 会创建 where 条件,例如 SELECT * FROM projects WHERE project_id = <project_id>

find_all_by_complete 然后在 and 条件下合并 complete = 0


我认为另一个可能有用的谜题是 @project.tasks 不仅仅是 Task 对象的简单数组,尽管如果您键入 project.tasks 在 Rails 控制台中。 project.tasks 实际上是一个 Active Record 关系对象(或者更准确地说是一个代理) 这有很多原因和好处,但主要的 2 个是它允许链接,它允许基础查询 运行 按需 if/when 需要。

关系有一系列关于如何委托方法调用的规则,其中之一是在关联对象的 class 上调用 class 方法。 (该关系知道它是与 Tasks 的关系)

所以当你写 tasks.find_incomplete 仍然等于 Task.find_incomplete 时你是正确的,除了当通过 project.tasks 调用 find_incomplete 时范围缩小到 project_id已经生效。