Benito Serna Tips and tools for Ruby on Rails developers

Association with default order, to fix the latest-comment n+1 problem

September 30, 2020

Do you have a method that works fine with a single record, but when you use it on a list causes N+1 queries?

Maybe, you are building a rails app and you want to fetch a list of posts with the latest comment (or something similar)…

You have a model like this…

class Posts
  has_many :comments

  def latest_comment
    comments.order(:created_at).last
  end
end

When you are fetching a single post there is no problem…

post = Post.find(id)
puts post.latest_comment

But when you try to fetch a list of posts, the method seems to ignore the includes and runs a query for each post to get the latest comment for each post =(.

Post.includes(:comments).each do |post|
  puts post.latest_comment
end

You can write a different method to get the latest comment with ruby (and not ActiveRecord), but it feels weird, and you know that it can bring you troubles later =(.

def self.latest_comment(post)
  post.comments.sort_by(&:created_at).last
end

Is this the only solution?

Well, nop… there are many ways to solve this =)

And although ther is no perfect solution, I want to tell you the one that I think that you should try first…

A default order for the association

On the definition of your association, you will pass a scope with the default order. It looks like this…

class Post
  has_many :comments, -> { order(:created_at) }

  def latest_comment
    comments.last
  end
end
Post.includes(:comments).each do |post|
  puts post.latest_comment
end

If you are not sure about the default order in the association, you can create a new association…

class Post
  has_many :comments
  has_many :sorted_comments, -> { order(:created_at) }, class_name: Comment

  def latest_comment
    sorted_comments.last
  end
end
Post.includes(:sorted_comments).each do |post|
  puts post.latest_comment
end

If you want to know more about how this part of the has_many association work, you can check with the “Scopes” section in the docs.

I hope this tip, can help you =)

Note: As as I said, this solution is not perfect, it can cause some performance problems if you have many comments per posts, because you are preloading all the coments for the list of posts, but don’t worry for now unless you will have many (maybe more than 50) comments per post =)

Want to know more ways to solve this problem?

If you want to review other options, you can see this post with other 4 ways to fix this problem.

Related articles

No more… “Why active record is ignoring my includes?”

Get for free the first part of the ebook Fix n+1 queries on Rails that will help you:

  • Explain what is an n+1 queries problem
  • Identify when ActiveRecord will execute a query
  • Solve the latest comment example
  • Detect n+1 queries by watching the logs
  • Learn the tools to detect n+1 queries
Get the first part of the ebook for free