62

Given the following AR models, I would like to sort users alphabetically by last name when given a handle to a task:

#user
has_many :assignments
has_many :tasks, :through => :assignments    

#assignment
belongs_to :task
belongs_to :user

#task
has_many :assignments
has_many :users, :through => :assignments

I would like to get a task then navigation to its assigned users, and sort the user list alphabetically.

I keep thinking that I should be able to add the :order clause to has_many :users, :through => :assignments like this:

#task.rb
has_many :assignments
has_many :users, :through => :assignments, :order => 'last_name, first_name'

however this does not work.

How can I sort users by last_name when given a task?

rswolff
  • 3,258
  • 5
  • 28
  • 31

8 Answers8

72

Since condition arguments are deprecated in Rails 4, one should use scope blocks:

has_many :users, -> { order 'users.last_name, users.first_name' }, :through => :assignments
Daniel
  • 4,082
  • 3
  • 27
  • 46
  • 1
    It looks like there is currently a bug in Rails 4 with this https://github.com/rails/rails/issues/17904 "In Rails 4.0, the order clause was ignored. In Rails 4.1, invalid SQL is generated." – Nate Dec 29 '14 at 13:35
  • It doesn't even give me that, just completely ignores the order clause with no message. – Wylliam Judd Aug 03 '16 at 18:04
37

Rails 3.x version:

has_many :users, :through => :assignments, :order => 'users.last_name, users.first_name'

UPDATE: This only works in Rails 3.x (maybe before that too). For 4+, see other answers.

Martin T.
  • 3,132
  • 1
  • 30
  • 31
12

The M.G.Palmer's approach works well, however table name is involved. There is a better way to do it:

has_many :users, :through => :assignments, :order => [ :last_name, :first_name ]
denis.peplin
  • 9,585
  • 3
  • 48
  • 55
  • 1
    but what if the order is determined by the join table? e.g. assignments ... but without defining a default scope on assignments.. – Tilo Feb 27 '18 at 07:28
7

This works for me (Rails 4.2)

Applying an ordering on the through map is not preserved, aka this is not enough to have the genres ordered:

has_many    :disk_genre_maps,
            -> {order('disk_genre_map.sort_order')},
            :inverse_of => :disk,
            :dependent  => :destroy,
            :autosave   => true


has_many    :genres,    # not sorted like disk_genre_maps
            :through    => :disk_genre_maps,
            :source     => :genre,
            :autosave   => true

So I overwrite this per instance:

def genres  # redefine genres per instance so that the ordering is preserved
    self.disk_genre_maps.map{|dgm|dgm.genre}
end

To make that work for assignments, this should be something like this (untested)

def genres= some_genres
    self.disk_genre_maps = some_genres.map.with_index do |genre, index|
        DiskGenreMap.new(disk:self, genre:genre, sort_order:index)
    end
end
edx
  • 1,317
  • 1
  • 12
  • 14
7

I am using Rails (5.0.0.1) and could make the sort with this syntax in my model Group that has many users through group_users:

# Associations.
has_many :group_users
has_many :users, -> { order(:name) }, through: :group_users

Adjust the code to your need that will work.

Tilo
  • 33,354
  • 5
  • 79
  • 106
Ricardo Emerson
  • 856
  • 10
  • 11
6

Would this work for you?

# User.rb

class User < ActiveRecord::Base
 default_scope :order => 'last_name ASC'
 ...
end

You can define other named scopes for when sorting needs to be different.

http://ryandaigle.com/articles/2008/11/18/what-s-new-in-edge-rails-default-scoping

Antony Sastre
  • 617
  • 8
  • 19
  • 2
    Sorry to bump an old question, but this might be relevant to future readers: Some plugins (acts_as_list for example) don't work properly with default_scope. –  May 08 '11 at 14:56
  • Also bumping this as I had a similar issue, but needed to sort on a field IN the :through table — the awesome thing is that default_scope works in the :through table as well, and records retrieved via that relationship will respect the order. – MBHNYC May 19 '12 at 21:04
  • 1
    Having `default_scope` is a very bad practice. There are many aricles written on this topic, for example: https://piechowski.io/post/why-is-default-scope-bad-rails/ – dpaluy Apr 09 '20 at 20:08
2

You could also create a new 'sort_order' column on the assignment table, and add a default scope like

default_scope { order('sort_order desc')}

to your assignments model.

Yogzzz
  • 2,735
  • 5
  • 36
  • 56
1

has_many :users, -> { order(:last_name, :first_name) }, :through => :assignments, source: 'user'

user223697
  • 193
  • 7