I would like to specify a default sort order in my model.
So that when I do a .where()
without specifying an .order()
it uses the default sort. But if I specify an .order()
, it overrides the default.
default_scope
This works for Rails 4+:
class Book < ActiveRecord::Base
default_scope { order(created_at: :desc) }
end
For Rails 2.3, 3, you need this instead:
default_scope order('created_at DESC')
For Rails 2.x:
default_scope :order => 'created_at DESC'
Where created_at
is the field you want the default sorting to be done on.
Note: ASC is the code to use for Ascending and DESC is for descending (desc
, NOT dsc
!).
scope
Once you're used to that you can also use scope
:
class Book < ActiveRecord::Base
scope :confirmed, :conditions => { :confirmed => true }
scope :published, :conditions => { :published => true }
end
For Rails 2 you need named_scope
.
:published
scope gives you Book.published
instead of
Book.find(:published => true)
.
Since Rails 3 you can 'chain' those methods together by concatenating them with periods between them, so with the above scopes you can now use Book.published.confirmed
.
With this method, the query is not actually executed until actual results are needed (lazy evaluation), so 7 scopes could be chained together but only resulting in 1 actual database query, to avoid performance problems from executing 7 separate queries.
You can use a passed in parameter such as a date or a user_id (something that will change at run-time and so will need that 'lazy evaluation', with a lambda, like this:
scope :recent_books, lambda
{ |since_when| where("created_at >= ?", since_when) }
# Note the `where` is making use of AREL syntax added in Rails 3.
Finally you can disable default scope with:
Book.with_exclusive_scope { find(:all) }
or even better:
Book.unscoped.all
which will disable any filter (conditions) or sort (order by).
Note that the first version works in Rails2+ whereas the second (unscoped) is only for Rails3+
So
... if you're thinking, hmm, so these are just like methods then..., yup, that's exactly what these scopes are!
They are like having def self.method_name ...code... end
but as always with ruby they are nice little syntactical shortcuts (or 'sugar') to make things easier for you!
In fact they are Class level methods as they operate on the 1 set of 'all' records.
Their format is changing however, with rails 4 there are deprecation warning when using #scope without passing a callable object. For example scope :red, where(color: 'red') should be changed to scope :red, -> { where(color: 'red') }
.
As a side note, when used incorrectly, default_scope can be misused/abused.
This is mainly about when it gets used for actions like where
's limiting (filtering) the default selection (a bad idea for a default) rather than just being used for ordering results.
For where
selections, just use the regular named scopes. and add that scope on in the query, e.g. Book.all.published
where published
is a named scope.
In conclusion, scopes are really great and help you to push things up into the model for a 'fat model thin controller' DRYer approach.
A quick update to Michael's excellent answer above.
For Rails 4.0+ you need to put your sort in a block like this:
class Book < ActiveRecord::Base
default_scope { order('created_at DESC') }
end
Notice that the order statement is placed in a block denoted by the curly braces.
They changed it because it was too easy to pass in something dynamic (like the current time). This removes the problem because the block is evaluated at runtime. If you don't use a block you'll get this error:
Support for calling #default_scope without a block is removed. For example instead of
default_scope where(color: 'red')
, please usedefault_scope { where(color: 'red') }
. (Alternatively you can just redefine self.default_scope.)
As @Dan mentions in his comment below, you can do a more rubyish syntax like this:
class Book < ActiveRecord::Base
default_scope { order(created_at: :desc) }
end
or with multiple columns:
class Book < ActiveRecord::Base
default_scope { order({begin_date: :desc}, :name) }
end
Thanks @Dan!
You can use default_scope to implement a default sort order http://api.rubyonrails.org/classes/ActiveRecord/Scoping/Default/ClassMethods.html
The accepted answer used to be correct - default scopes were the only way to do this. However, default scopes are actually chained (the scope itself is applied to any further scopes/calls), and this can cause some unpredictable behavior. Default scopes are thus widely considered something to avoid at almost all cost.
It's important to note that given your original question, default scope doesn't actually quite satisfy this, since in order to actually skip the default scope, any model has to explicitly specify .unscoped
first. That means that if order(:something)
is called, without .unscoped
, the result is actually more similar to order(:default).order(:something)
. :something
takes priority, sure, but :default
is still there, so now there's a multicolumn sort, which might not be desired. The original question specifies that the default sort order be ignored if another order is called, and default scope, without .unscoped
, doesn't meet that requirement.
Since Rails 6, however, there is now implicit_order_column
, which can be set on a model.
class Book < ApplicationRecord
self.implicit_order_column = 'publish_date'
end
This will cause the model to order by that column by default, instead of the primary key. Like the default, built in sort (which uses id), however, if a different order is specified (with order(:something)
), this implicit ordering is ignored, not chained: .order(:something)
does not result in a multicolumn sort, and the need for .unscoped
is gone.
One of the most common uses of this is when switching to UUID's as primary keys. By default, Rails will still order on the primary key, but since that key is now a meaningless byte string, this order is now similarly meaningless. Thus to replicate the old behavior, such that SomeModel.last
should return the most recently created record by default, simply set this on ApplicationRecord:
class ApplicationRecord < ActiveRecord::Base
self.implicit_order_column = 'created_at'
end
Note that it is theoretically possible to cause an error here. created_at
is not unique - it's stored with microsecond precision, but in theory it's possible to create to records with the exact same created_at
time, and thus, any returned results that depended only on that would be non-deterministic - subsequent calls might return different records. It is a low enough chance, however, that it's often considered safe to do this - particularly if the code does not depend on a deterministic result here (which, it probably shouldn't - if deterministic results are needed, referencing the primary key or some other unique column is better when possible).
It's also worth noting that created_at
(or whatever column is used), by default, does not have a key. This means selects will slightly slow down doing this, at least until a key is added to that column to fix this.
Finally, implicit_order_column
has limited functionality - it is not possible to set even the sort order, much less to do more complex things like multi-column sorts. If that functionality is required, default_scope
is still the way only to go.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With