Default order for relations

At Discourse, we would like to have all the posts of a topic ordered by their post number by default. Basically, we want to have something like:

class Post < ActiveRecord::Base
  belongs_to :topic
end

# == Schema Information
#
# Table name: posts
#
#  id                      :integer          not null, primary key
#  topic_id                :integer          not null
#  post_number             :integer          not null
class Topic < ActiveRecord::Base
  has_many :posts, -> { order(:post_number) }
end

The problem is that because post_number is unique for posts in same topic, any further orders on posts are useless. Let’s consider topic to be an instance of Topic, then topic.posts.order(:created_at) will generate a clause ORDER BY post_number, created_at.

One solution to this is to use reorder instead of order, however I consider that something one could forget and cause a couple of WTFs. Another solution is to use implicit_order_column = 'post_number', but this would break other things like Post.last, which will no longer return the last created post, but the last post from the longest topic. What we would like to have is an implicit order for relations similar to implicit_order_column.

I decided to implement a solution to this and added implicit_order query method, which defines an implicit order which will be used if no other order is defined. Using it, the example above becomes:

class Topic < ActiveRecord::Base
  has_many :posts, -> { implicit_order(:post_number) }
end

topic.posts # ORDER BY post_number
topic.posts.order(:created_at) # ORDER BY created_at

I believe that this could replace even implicit_order_column if one would use default_scope and implicit_order together.

https://github.com/rails/rails/pull/39525

Sorry for being a bit late to the party. :flushed:

8 Likes

Cool feature! Just sharing a thought based on experience with ordering in a relation default scope: you may still have to remove the order sometimes because you can’t order by another column when you use an aggregate function.

I think this is not an issue since that problem already exists when you use a regular order in a relation scope, but will your implementation still allow to clear the ordering with order(‘’) or unscope(:order)?

See ruby on rails - Remove order from ActiveRecord scope - Stack Overflow

2 Likes

One suggestion from @rafaelfranca was to introduce the API of:

has_many :posts, implicit_order(:post_number)

The reason here is that it leave AR relation clean. If we leave this on Relation then we have to deal with stacking of implicit orders and other complications.

@udan11 can you give that a shot?

1 Like

Perhaps it would make more sense as an option, so as to keep the lambda argument?

has_many :posts, -> { include(:user) }, implicit_order: :post_number
1 Like

Would this also work as an alternative to default_scope -> { order(...) } ? e.g. default_scope -> { implicit_order(...) }

I’ve been bitten in the past with default scopes for ordering. Primarily when joining or trying to force an order, like the example above.

1 Like