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
相关产品推荐
相关产品推荐

