Prevent N+1 Queries in Rails Controllers with ActiveRecord includes
Learn how to eager‑load associations in Rails controller actions to eliminate N+1 query problems, verify the SQL log, and keep memory usage under control.
28 Aug 2025, 19:30 UTC

Desired outcome
Load parent records and their associated children in a minimal number of SQL queries so that rendering a collection does not trigger an extra query for each parent (the classic N+1 problem).
Prerequisites
- A Rails application (version 5.0 or later) with ActiveRecord models that define associations such as
has_manyorbelongs_to. - A controller action that retrieves a collection of parent objects and iterates over them to render associated data in a view.
- Development environment with query logging enabled (
config.log_level = :debug).
Procedure
- Identify the N+1 pattern
In the controller, note the query that loads the parents, e.g.
@posts = Post.all. In the view, you likely callpost.commentsinside a loop. - Add eager loading
Modify the query to include the association:
# app/controllers/posts_controller.rb class PostsController < ApplicationController def index @posts = Post.includes(:comments) # eager loads comments end end - Verify the generated SQL
Start the server in development mode and request the action. The log should show two SELECT statements: one for
postsand one forcomments(typically a LEFT OUTER JOIN or a separate query). - Ensure the view uses preloaded data
Access the association as usual; no additional SELECT should appear while rendering each parent.
<% @posts.each do |post| %> <h3><%= post.title %></h3> <ul> <% post.comments.each do |comment| %> <li><%= comment.body %></li> <% end %> </ul> <% end %>
Expected checks
- Review the development log: you should see exactly two SELECT statements (parents and children) and no further SELECTs inside the view rendering loop.
- Optionally add the
bulletgem to the Gemfile (gem 'bullet') and runbundle install. Bullet will raise an alert if any N+1 queries remain. - Use
rack-mini-profilerto confirm that the total query count stays low across requests.
Limitations and practical verification
- Memory usage: Eager loading fetches all associated rows at once. For large collections, add pagination (
limit/offsetor a gem likekaminari) to keep result sets manageable. - Polymorphic associations:
includescannot eager load a polymorphic association without specifying the concrete type. Use a hash syntax, e.g.Post.includes(comments: :author)or join tables when appropriate. - Verification: After each change, restart the Rails server (or reload in development) and repeat the log check. If extra queries appear, double‑check that the symbol passed to
includesmatches the association name exactly as defined in the model.
Recovery options
- If the log shows additional SELECTs, verify the association name and ensure it is not misspelled.
- For polymorphic cases, replace
includes(:comments)withincludes(comments: :author)or useeager_loadwith joins. - Re‑run the request and confirm the log now contains only the expected two SELECT statements.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.