4

I write Hi, but SO wouldn't let it, so I write longer sentence :) Hi by the way.

It looks like my scope is not working.

I wrote that scope:

scope :ordered, ->(field, order) { except(:order).order("#{field} #{order}") }

but it returns the following when checking SQL:

irb >p.levels.ordered("name", "ASC").to_sql
=> "SELECT \"levels\".* FROM \"levels\" WHERE (\"levels\".pie_id = 6 AND (\"levels\".\"parent_id\" = 0)) ORDER BY position ASC, name ASC"

NOTE: position ASC shouldn't be there

But it works when adding except before my scope...

irb > p.levels.except(:order).ordered("name", "ASC").to_sql
 => "SELECT \"levels\".* FROM \"levels\" WHERE (\"levels\".pie_id = 6 AND (\"levels\".\"parent_id\" = 0)) ORDER BY name ASC" `

Is except available in a scope ? Or do you see anything that could help me please?

Ruby 1.9.2p290

Rails 3.0.14

Thx

Bachet
  • 321
  • 1
  • 2
  • 14

3 Answers3

3

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:

Serge Balyuk
  • 3,442
  • 1
  • 23
  • 23
1

Added scope that u defined in the model

scope :ordered, ->(field, order) { except(:order).order("#{field} #{order}") }

Tried different combinations and all work

a.inspection_serial_numbers.ordered('part_serial_number', 'DESC').except(:order).ordered('id', 'DESC').to_sql

=> "SELECT `inspection_serial_numbers`.* FROM `inspection_serial_numbers` WHERE  `inspection_serial_numbers`.`inspection_master_id` = 1 ORDER BY id DESC" 

a.inspection_serial_numbers.ordered('part_serial_number', 'DESC').except(:order).ordered('id', 'DESC').except(:order).ordered('is_active', 'ASC').to_sql

=> "SELECT `inspection_serial_numbers`.* FROM `inspection_serial_numbers`  WHERE `inspection_serial_numbers`.`inspection_master_id` = 1 ORDER BY is_active ASC" 

Even if u combine the 'ordered' scope with 'except' in any combinations a number of times, the last 'ordered' scope is used for ordering

a.inspection_serial_numbers.ordered('part_serial_number', 'DESC').ordered('id', 'ASC').except(:order).ordered('id', 'DESC').to_sql

=> "SELECT `inspection_serial_numbers`.* FROM `inspection_serial_numbers`  WHERE `inspection_serial_numbers`.`inspection_master_id` = 1 ORDER BY id DESC" 

Buf it u want to remove all the scopes, use 'unscoped'

a.inspection_serial_numbers.ordered('id', 'ASC').unscoped.ordered('part_serial_number', 'DESC').to_sql

=> "SELECT \"inspection_serial_numbers\".* FROM \"inspection_serial_numbers\"  ORDER BY part_serial_number DESC" 

see http://apidock.com/rails/ActiveRecord/SpawnMethods/except and http://apidock.com/rails/ActiveRecord/Base/unscoped/class

Prasad Surase
  • 6,486
  • 6
  • 39
  • 58
0

the order of method may be wrong.

it should be write order("#{field} #{order}").except(:order)

see rails guide

utwang
  • 1,474
  • 11
  • 16
  • What I want to achieve is to reset the default order condition to set a new order right after. The way you show is removing the order you just set. Then, the query won't have any order condition. See the link you gave. Thx anyway – Bachet Aug 19 '12 at 21:11