Typically, when we  wish to loop with a ruby Activerecord object, we follow something like below :


class CommentsController < ApplicationController
  def users_comments
    posts = Post.all
    comments = posts.map(&:comments).flatten
    @user_comments = comments.select do |comment|
      comment.author.username == params[:username]

While above will be correct and won't create any issue as such,  but this will result in multiple unnecessary calls to Database. This is a classic case of what we call "n+1" BUG.

The first line will retrieve all records of the Posts table in Post object from the database, but then next line will make an additional request for each Post object to fetch the corresponding Comment objects related to that relevant Post. To make matters worse, this code is then making even more database requests in order to retrieve the Author of each Comment of each Post.

This can all be avoided by changing the first line in the method to:

posts = Post.includes(comments: [:author]).all

This tells ActiveRecord to retrieve the corresponding Comment and Author records from the database immediately after the initial request for all Posts, thereby reducing the number of database requests to just three.

Please note that the above solution is only one of a few ways that it is possible to avoid incurring an “n+1” penalty, and each alternative will have its own besr and worse cases. The above answer is shared since it requires the smallest change to the existing code and makes no assumptions regarding the reverse association of Comment to Post.

Apparently, there’s another issue here (although not what we’re focused on in this question and answer); namely, performing a query in Ruby that could instead be done in the database (and which would very likely be faster there!). A relatively complex query like this can instead be constructed in ActiveRecord pretty easily, thus turning a 3 database query operation (plus some Ruby code executing) into a single database query.



Hopefully this will help you in using your loops to a better extent and reduce usage of plain/default Object.all method.

Good practices to follow in Ruby on Rails

Leave a Reply

Your email address will not be published.