Reputation: 19863
This seems like a really simple question but I haven't seen it answered anywhere.
In rails if you have:
class Article < ActiveRecord::Base
has_many :comments
end
class Comments < ActiveRecord::Base
belongs_to :article
end
Why can't you order the comments with something like this:
@article.comments(:order=>"created_at DESC")
Named scope works if you need to reference it a lot and even people do stuff like this:
@article.comments.sort { |x,y| x.created_at <=> y.created_at }
But something tells me it should be simpler. What am I missing?
Upvotes: 114
Views: 71913
Reputation: 10695
As of Rails 4, you would do:
class Article < ActiveRecord::Base
has_many :comments, -> { order(created_at: :desc) }
end
class Comment < ActiveRecord::Base
belongs_to :article
end
For a has_many :through
relationship the argument order matters (it has to be second):
class Article
has_many :comments, -> { order('postables.sort' :desc) },
:through => :postable
end
If you will always want to access comments in the same order no matter the context you could also do this via default_scope
within Comment
like:
class Comment < ActiveRecord::Base
belongs_to :article
default_scope { order(created_at: :desc) }
end
However this can be problematic for the reasons discussed in this question.
Before Rails 4 you could specify order
as a key on the relationship, like:
class Article < ActiveRecord::Base
has_many :comments, :order => 'created_at DESC'
end
As Jim mentioned you can also use sort_by
after you have fetched results although in any result sets of size this will be significantly slower (and use a lot more memory) than doing your ordering through SQL/ActiveRecord.
If you are doing something where adding a default order is cumbersome for some reason or you want to override your default in certain cases, it is trivial to specify it in the fetching action itself:
sorted = article.comments.order('created_at').all
Upvotes: 61
Reputation: 274
And if you need to pass some additional arguments like dependent: :destroy
or whatever, you should append the ones after a lambda, like this:
class Article < ActiveRecord::Base
has_many :comments, -> { order(created_at: :desc) }, dependent: :destroy
end
Upvotes: 4
Reputation: 5486
If you are using Rails 2.3 and want to use the same default ordering for all collections of this object you can use default_scope to order your collection.
class Student < ActiveRecord::Base
belongs_to :class
default_scope :order => 'name'
end
Then if you call
@students = @class.students
They will be ordered as per your default_scope. TBH in a very general sense ordering is the only really good use of default scopes.
Upvotes: 7
Reputation: 81052
You can specify the sort order for the bare collection with an option on has_many
itself:
class Article < ActiveRecord::Base
has_many :comments, :order => 'created_at DESC'
end
class Comment < ActiveRecord::Base
belongs_to :article
end
Or, if you want a simple, non-database method of sorting, use sort_by:
article.comments.sort_by &:created_at
Collecting this with the ActiveRecord-added methods of ordering:
article.comments.find(:all, :order => 'created_at DESC')
article.comments.all(:order => 'created_at DESC')
Your mileage may vary: the performance characteristics of the above solutions will change wildly depending on how you're fetching data in the first place and which Ruby you're using to run your app.
Upvotes: 159
Reputation: 21397
You can use ActiveRecord's find method to get your objects and sort them too.
@article.comments.find(:all, :order => "created_at DESC")
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
Upvotes: 6