Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I automatically sort a has_many relationship in Rails?

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?

like image 827
Brian Armstrong Avatar asked Apr 10 '09 21:04

Brian Armstrong


People also ask

What is dependent destroy?

If you set the :dependent option to: :destroy, when the object is destroyed, destroy will be called on its associated objects. :delete, when the object is destroyed, all its associated objects will be deleted directly from the database without calling their destroy method.


2 Answers

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.

like image 144
Jim Puls Avatar answered Sep 19 '22 05:09

Jim Puls


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 
like image 24
Matt Sanders Avatar answered Sep 19 '22 05:09

Matt Sanders