Reputation: 3328
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?
Upvotes: 62
Views: 38634
Reputation: 293
has_many :users, -> { order(:last_name, :first_name) }, :through => :assignments, source: 'user'
Upvotes: 1
Reputation: 906
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.
Upvotes: 7
Reputation: 3222
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.
Upvotes: 37
Reputation: 1337
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
Upvotes: 7
Reputation: 4173
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
Upvotes: 72
Reputation: 2785
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.
Upvotes: 2
Reputation: 9841
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 ]
Upvotes: 12
Reputation: 617
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
Upvotes: 6