You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Active Record中includes作为类方法的工作原理解析

为什么Active Record的includes是实例方法却能在模型类上调用?

这是个非常敏锐的观察!你说的完全没错——includes确实是定义在ActiveRecord::Relation的实例方法里(就是你在lib/active_record/relation/query_methods.rb里追踪到的那个),但我们能像User.includes(:images)这样在模型类上下文调用它,背后是Active Record精心设计的查询接口统一机制在运作,拆解开来其实很清晰:

1. 模型类的查询方法本质是转发给Relation实例

当你在模型类(比如User)上调用includes这类查询方法时,模型类本身并没有直接定义这个方法。Active Record通过ActiveRecord::Querying模块(这个模块被混入到ActiveRecord::Base中,所有模型类都继承自它)实现了方法转发:

  • 当你调用User.includes(:images)时,底层会先隐式调用User.all,而all方法会返回一个ActiveRecord::Relation实例(这个实例代表了“查询所有User记录”的待执行查询)。
  • 紧接着,就会在这个Relation实例上调用includes方法——也就是你看到的那个实例方法。

简单来说,User.includes(:images)等价于User.all.includes(:images),只是Active Record帮你省略了中间的all调用,让API更简洁直观。

2. 统一查询接口的设计优势

这种把所有查询逻辑集中到ActiveRecord::Relation类的设计,带来两个核心好处:

  • 代码一致性:不管是从模型类发起查询,还是在已有的Relation上做链式调用(比如User.where(active: true).includes(:images)),调用的都是同一个includes实例方法,不需要重复实现类方法和实例方法两套逻辑。
  • 不可变的查询对象:你看到的spawn方法会创建一个新的Relation实例,而不是修改原有的实例——这样每次调用查询方法都会返回一个新的查询对象,避免了链式调用中的副作用,让代码更可靠。

3. 底层的方法拦截逻辑

具体来说,ActiveRecord::Querying模块通过重写method_missing或者直接定义转发方法(不同Rails版本实现细节略有不同),拦截了所有属于查询方法的调用。当检测到调用的是includes、where、order这类查询方法时,就会自动创建Relation实例并完成方法转发。

内容的提问来源于stack exchange,提问作者shampoo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 07:17:04