You probably have already figured that you can achieve your goal by using reorder
. So here's my theory for why reorder
works and except
doesn't.
It's important, that methods like order
, where
, except
are handled by instances of ActiveRecord::Relation
, while scopes, e.g. ordered
from your example, are delegated by an instance of ActiveRecord::Relation
to your model class.
some_relation.order(:x)
method simply returns fresh copy of some_relation
with :x
added to its list of order_values
. Likewise, some_relation.except(:order)
would return a copy of some_relation
with empty order_values
. As long as chain of calls consists of such relation methods, except
works as we'd expect.
A call to a scope method, when scope is implemented as lambda returning relation, ends up with merge of model's relation returned by scoped
with relation returned by the lambda:
scopes[name] = lambda do |*args|
options = scope_options.is_a?(Proc) ? scope_options.call(*args) : scope_options
relation = if options.is_a?(Hash)
scoped.apply_finder_options(options)
elsif options
scoped.merge(options) # <- here options is what returned by your :ordered lambda
else
scoped
end
extension ? relation.extending(extension) : relation
end
And this merge
doesn't preserve effect of except
if it was done only to one of relations being merged. If we merge a
and b
, and b
doesn't have order set, but a
does, the result would still have order. Now reorder
works around it with a trick: it sets special flag reorder_flag
on relation which controls how merge
carries order_values
.
Here's my test sample. I'm using default_scope
to inject order into Product#scoped
. In your example, order is probably injected into Level#scoped
by association in Pie
, which can look like has_many :levels, :order => 'position'
.
class Product < ActiveRecord::Base
default_scope order('id DESC')
scope :random_order, lambda {
r = order('random()')
puts "from lambda: " + r.order_values.inspect
r
}
end
# in console:
>> Product.scoped.order_values
=> ["id DESC"]
>> Product.random_order.order_values
from lambda: ["id DESC", "random()"]
=> ["id DESC", "id DESC", "random()"]
# now if I change the first line of lambda to
# r = except(:order).order('random()')
>> Product.random_order.order_values
from lambda: ["random()"]
=> ["id DESC", "random()"]
As you can see, because of the fact that Product.scoped
has id DESC
order, it appears in result despite it was cleared from relation returned by scope.
Here's the list of links to relevant sources: