eager loading not working with order() clause in rails

You can achieve flexibility by using built-in method ActiveRecord::Associations::Preloader#preload.

It accept three arguments:

preload(records, associations, preload_scope = nil)

The first argument accepts ActiveRecord::Base record or array of records. Second is one or more associations that you want preload to records specified in the first argument. Last is Hash or Relation which merged with associations.

Use third argument to get sorted and preloaded associations:

users = User.order(user: :id)

ActiveRecord::Associations::Preloader.new.preload(
  users,
  :skills,
  { order: :name } # or Skill.order(:name)
)

When you eager load associated records using .includes, you should access the association as it is. Otherwise, if you add more query conditions to the association, that will cause a new DB query.

There are a few ways how you can order the associated eager loaded records.

1. Add order condition to the main scope.

user = User.includes(:skills).order("users.id, skills.name ASC")

In this case, it won't work like include method works by default, making two queries. One query will be performed using 'LEFT OUTER JOIN' to fetch the associated records. This is equivalent to using the eager_load method instead of includes

user = User.eager_load(:skills).order("users.id, skills.name ASC")

2. Add order condition to association when you define it.

In this case whenever you access the association, the associated records will always be ordered by name.

class User < ActiveRecord::Base
  has_many :skills, -> { order(:name) }
end

3. Create another association with required order for using only in this particular case.

This allows you to avoid unnecessary conditions on the main association which is skills.

class User < ActiveRecord::Base
  has_many :skills_ordered_by_name, -> { order(:name) }, class_name: "Skill"
end

# usage
users = User.includes(:skills_ordered_by_name)

users.each do |user|
  # access eager loaded association
  user.skills_ordered_by_name
end

4. Set default order for the association model.

This will cause the condition to be applied to every association and query related to the associated model.

class Skill < ActiveRecord::Base
  default_scope { order(:name) }
end

5. Sort eager loaded records using Ruby code (not ActiveRecord query methods)

This approach is appropriate when there are not many records to sort.

users = User.includes(:skills)

users.each do |user|
  # sorting with Ruby's 'sort_by' method
  user.skills.sort_by(&:name)

  # or something like
  user.skills.sort { |one, another| one.name <=> another.name }
end