Fix Bullet::Notification::UnoptimizedQueryError: USE eager loading detected Post => [:comments] in Rails
An N+1 query occurs when your code loads a collection and then makes a separate database query for each item's associations. This drastically slows page loads as the number of records grows. Fix it by using includes, preload, or eager_load to batch-load the associated records in a single query.
Reading the Stack Trace
Here's what each line means:
- app/views/posts/index.html.erb:5: The view iterates over posts and accesses post.comments on each iteration, triggering individual queries.
- app/controllers/posts_controller.rb:8:in `index': The controller loads posts without eager loading the comments association.
- activerecord (7.1.3) lib/active_record/relation.rb:860:in `exec_queries': ActiveRecord executes a separate query for each post's comments instead of batching.
Common Causes
1. Missing includes on association
The controller fetches posts but does not eager load comments, causing a query per post when the view accesses comments.
# app/controllers/posts_controller.rb
def index
@posts = Post.all
end
# app/views/posts/index.html.erb
<% @posts.each do |post| %>
<p><%= post.comments.count %> comments</p>
<% end %>
2. Nested associations not preloaded
Eager loading one level but not the nested association still causes N+1 at the deeper level.
@posts = Post.includes(:comments)
# But view also accesses comment.user which is not preloaded
<% post.comments.each do |comment| %>
<p><%= comment.user.name %></p>
<% end %>
3. Counter cache not used
Using association.count instead of a counter cache column forces a COUNT query for each record.
<% @posts.each do |post| %>
<p><%= post.comments.count %></p> <!-- COUNT query per post -->
<% end %>
bugstack fixes this class of error automatically — in under 2 minutes.
Start Free Trial →The Fix
Adding .includes(:comments) tells ActiveRecord to load all comments for all posts in a single query using a LEFT OUTER JOIN or a separate IN query, eliminating the N+1 problem.
def index
@posts = Post.all
end
def index
@posts = Post.includes(:comments).order(created_at: :desc)
end
Testing the Fix
require 'rails_helper'
RSpec.describe PostsController, type: :controller do
describe 'GET #index' do
it 'eager loads comments to avoid N+1' do
posts = create_list(:post, 3)
posts.each { |p| create_list(:comment, 2, post: p) }
expect {
get :index
}.to make_database_queries(count: 2..3)
end
it 'returns all posts' do
create_list(:post, 3)
get :index
expect(assigns(:posts).size).to eq(3)
end
end
end
Run your tests:
bundle exec rspec spec/controllers/posts_controller_spec.rb
Pushing Through CI/CD
git checkout -b fix/rails-n-plus-one
git add app/controllers/posts_controller.rb
git commit -m "fix: add includes(:comments) to eliminate N+1 query"
git push origin fix/rails-n-plus-one
Your CI config should look something like this:
name: CI
on:
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: postgres
ports: ['5432:5432']
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
- run: bin/rails db:setup
- run: bundle exec rspec
The Full Manual Process: 18 Steps
Here's every step you just went through to fix this one bug:
- Notice the error alert or see it in your monitoring tool
- Open the error dashboard and read the stack trace
- Identify the file and line number from the stack trace
- Open your IDE and navigate to the file
- Read the surrounding code to understand context
- Reproduce the error locally
- Identify the error source
- Write the fix
- Run the test suite locally
- Fix any failing tests
- Write new tests covering the edge case
- Run the full test suite again
- Create a new git branch
- Commit and push your changes
- Open a pull request
- Wait for code review
- Merge and deploy to production
- Monitor production to confirm the error is resolved
Total time: 30-60 minutes. For one bug.
Or Let bugstack Fix It in Under 2 minutes
Every step above? bugstack does it automatically.
Step 1: Install the SDK
gem install bugstack
Step 2: Initialize
require 'bugstack'
Bugstack.init(api_key: ENV['BUGSTACK_API_KEY'])
Step 3: There is no step 3.
bugstack handles everything from here:
- Captures the stack trace and request context
- Pulls the relevant source files from your GitHub repo
- Analyzes the error and understands the code context
- Generates a minimal, validated fix
- Runs your existing test suite
- Pushes through your CI/CD pipeline
- Deploys to production (or opens a PR for review)
Time from error to fix deployed: Under 2 minutes.
Human involvement: zero.
Try bugstack Free →No credit card. 5-minute setup. Cancel anytime.
Deploying the Fix (Manual Path)
- Add includes to the controller query.
- Enable Bullet gem in development and test to detect future N+1 queries.
- Run the test suite and confirm query count is reduced.
- Open a pull request.
- Merge and monitor query performance in production.
Frequently Asked Questions
BugStack runs the fix through your existing test suite, generates additional edge-case tests, and validates that no other components are affected before marking it safe to deploy.
BugStack never pushes directly to production. Every fix goes through a pull request with full CI checks, so your team can review it before merging.
includes lets Rails decide the strategy. preload always uses separate queries. eager_load always uses LEFT OUTER JOIN. Use eager_load when you need to filter on the association.
Bullet catches most common N+1 patterns but may miss complex cases involving scopes or custom SQL. Pair it with query log analysis for thorough coverage.
Stop fixing Ruby errors manually.
bugstack catches runtime errors, writes the fix, and opens a tested PR — in under 2 minutes.