When I monkey patch ActiveRecord::Base with class methods, the methods are inherited by the different model ActiveRecord_Relation classes (like User::ActiveRecord_Relation) and can be called on instances of specific active record relations. But this causes some unexpected behavior when making active record calls for the original model.
Here's a trivial example:
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
What is causing this behavior?
I know that monkey patching like this is a pretty bad idea for anything serious. I accidentally discovered this behavior and I'm very curious to know why it's happening.
The key to this question is in delegation.rb
Basically this has the follow method missing implementation for Relation (simplified slightly for brevity)
def method_missing(method,*args,&block)
scoping { @klass.public_send(method, *args, &block) }
end
(@klass is the active record class the relation belongs to)
The scoping method sets the class' current_scope
for the duration of the block. This contains things like where clauses, sorts etc. This is what allows you to call class methods on relations and have those class methods operate on the scope defined by the relation.
In the book case this is still happening, however the scoping is happening to Book but the query is against User so the scoping doesn't change the result.