I found a rather serious problem with named scopes in
ActiveRecord 2.3.X. After analyzing my logs I noticed that chaining
named scopes actually executes extra unscoped queries along the
way. Which is a big deal, especially if they are loading thousands
of records.
The fix is actually very easy, but it involved removing code,
and I have no idea what the purpose of the code is. Take the
original method:
def named_scope(name, options = {}, &block)
name = name.to_sym
scopes[name] = lambda do |parent_scope, *args|
Scope.new(parent_scope, case options
when Hash
options
when Proc
if self.model_name != parent_scope.model_name
options.bind(parent_scope).call(*args)
else
options.call(*args)
end
end, &block)
end
singleton_class.send :define_method, name do |*args|
scopes[name].call(self, *args)
end
end
Notice this line:
if self.model_name != parent_scope.model_name
The "parent_scope.model_name" actually executes a query, because
parent scope can be a proxy that delegates its method calls off to
the query result. So doing something like:
user = User.first
user.orders.state_equals("pending").total_greater_than(100).count
The last line should execute a single "count" query. Instead,
because of line mentioned above, it actually executes a "select
all" for the users orders with state "pending". Then it executes
the count query.
The fix ix very easy. Change this:
if self.model_name != parent_scope.model_name
options.bind(parent_scope).call(*args)
else
options.call(*args)
end
To this:
if self.model_name != parent_scope.model_name
options.bind(parent_scope).call(*args)
else
options.call(*args)
end
Again, I have no idea what the line in question does. Hence the
reason I didn't create a pull request on github.