为什么 ActiveRecord::Base 上的猴子修补方法会在 Model.count 上重新定义不相关的方法

Why does monkey patching methods on ActiveRecord::Base redefine unrelated methods on Model.count

当我使用 class 方法对 ActiveRecord::Base 进行猴子修补时,这些方法由不同的模型 ActiveRecord_Relation classes(如 User::ActiveRecord_Relation)继承,并且可以在特定活动记录关系的实例上调用。但这会在对原始模型进行活动记录调用时导致一些意外行为。

这是一个简单的例子:

User.count
=> 3544

users = User.where("created_at > ?", 1.month.ago)
users.count
=> 174

class ActiveRecord::Base
    def self.monkey_foo(options = {}, &block)
        User.count
    end
end

User.monkey_foo
=> 3544

Book.monkey_foo # Another model
=> 3544

users.monkey_foo
=> 173 # This is the count of the users relation, not the User model

Book.where("created_at > 1.year.ago").monkey_foo
=> 3544 # Issue only affects User model relations

是什么导致了这种行为?

我知道像这样的猴子修补对于任何严肃的事情来说都是一个非常糟糕的主意。我无意中发现了这种行为,我很好奇为什么会这样。

这道题的关键在delegation.rb

基本上这有以下方法缺少关系的实现(为简洁起见稍微简化)

def method_missing(method,*args,&block)
  scoping { @klass.public_send(method, *args, &block) }
end

(@klass 是活动记录 class 关系所属)

作用域方法为块的持续时间设置 class' current_scope。这包含诸如 where 子句、排序等内容。这就是允许您在关系上调用 class 方法并让这些 class 方法在关系定义的范围内运行的原因。

在 book 的情况下,这仍然发生,但是 Book 正在发生范围界定,但查询是针对 User 的,因此范围界定不会改变结果。