40

I have two models in a has_many relationship such that Log has_many Items. Rails then nicely sets up things like: some_log.items which returns all of the associated items to some_log. If I wanted to order these items based on a different field in the Items model is there a way to do this through a similar construct, or does one have to break down into something like:

Item.find_by_log_id(:all,some_log.id => "some_col DESC")

6 Answers 6

75

There are multiple ways to do this:

If you want all calls to that association to be ordered that way, you can specify the ordering when you create the association, as follows:

class Log < ActiveRecord::Base
  has_many :items, :order => "some_col DESC"
end

You could also do this with a named_scope, which would allow that ordering to be easily specified any time Item is accessed:

class Item < ActiveRecord::Base
  named_scope :ordered, :order => "some_col DESC"
end

class Log < ActiveRecord::Base
  has_many :items
end

log.items # uses the default ordering
log.items.ordered # uses the "some_col DESC" ordering

If you always want the items to be ordered in the same way by default, you can use the (new in Rails 2.3) default_scope method, as follows:

class Item < ActiveRecord::Base
  default_scope :order => "some_col DESC"
end
3
  • 12
    Since Rails 3.x, the named_scope syntax is slightly different. It is now called using "scope" instead of "named_scope", and uses functions to define the scope structure. For instance : "scope :ordered, order("some_col DESC")".
    – Péha
    Commented Nov 16, 2011 at 20:17
  • 14
    In Rails 4 there’s another approach again. The default association scope should be specified as a lambda like has_many :items, ->{ order(:some_col).where(foo: 'bar') } and, similarly, named scopes now take a lambda scope :name_of_scope, ->{ where(foo: 'bar') }. The default scope takes a block: default_scope: { where(foo: 'bar') }
    – Leo
    Commented Oct 24, 2013 at 15:32
  • Superb answer. +1
    – sscirrus
    Commented Mar 5, 2018 at 21:37
17

rails 4.2.20 syntax requires calling with a block:

class Item < ActiveRecord::Base
  default_scope { order('some_col DESC') }
end

This can also be written with an alternate syntax:

default_scope { order(some_col: :desc) }
4

Either of these should work:

Item.all(:conditions => {:log_id => some_log.id}, :order => "some_col DESC")
some_log.items.all(:order => "some_col DESC")
3

set default_scope in your model class

class Item < ActiveRecord::Base
  default_scope :order => "some_col DESC"
end

This will work

1
  • 2
    @SsouLlesS This has answered in 2011 when there was no Rails 4.
    – Sayuj
    Commented Dec 16, 2015 at 10:38
0

order by direct relationship has_many :model

is answered here by Aaron

order by joined relationship has_many :modelable, through: :model

class Tournament
  has_many :games # this is a join table
  has_many :teams, through: :games

  # order by :name, assuming team has this column
  def teams
    super.order(:name)
  end
end

Tournament.first.teams # are returned ordered by name
0

For anyone coming across this question using more recent versions of Rails, the second argument to has_many has been an optional scope since Rails 4.0.2. Examples from the docs (see scopes and options examples) include:

has_many :comments, -> { where(author_id: 1) }
has_many :employees, -> { joins(:address) }
has_many :posts, ->(blog) { where("max_post_length > ?", blog.max_post_length) }
has_many :comments, -> { order("posted_on") }
has_many :comments, -> { includes(:author) }
has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person"
has_many :tracks, -> { order("position") }, dependent: :destroy

As previously answered, you can also pass a block to has_many. "This is useful for adding new finders, creators and other factory-type methods to be used as part of the association." (same reference - see Extensions).

The example given there is:

has_many :employees do
  def find_or_create_by_name(name)
    first_name, last_name = name.split(" ", 2)
    find_or_create_by(first_name: first_name, last_name: last_name)
  end
end

In more modern Rails versions the OP's example could be written:

class Log < ApplicationRecord
  has_many :items, -> { order(some_col: :desc) }
end

Keep in mind this has all the downsides of default scopes so you may prefer to add this as a separate method:

class Log < ApplicationRecord
  has_many :items

  def reverse_chronological_items
    self.items.order(date: :desc)
  end
end

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.