π Using Bullet Gem the Right Way for Maximum Efficiency in Ruby on Rails
β‘ Stop N+1 Queries Before They Destroy Your Rails App
π Using Bullet Gem the Right Way for Maximum Efficiency in Ruby on Rails

β‘ Stop N+1 Queries Before They Destroy Your Rails App
If youβve ever opened your Rails logs and seen dozens (or hundreds π±) of repeated SQL queries, congratulations β youβve met the infamous N+1 Query Problem.
Rails makes database interactions beautifully simple, but without optimization, performance can silently collapse as your application grows.
Thatβs where the legendary Ruby gem comes in:
π What is Bullet Gem?
The Bullet gem helps detect:
β N+1 queries β Unused eager loading β Missing counter caches
It acts like a performance watchdog πΆ for your Rails application.
Instead of discovering slow pages in production, Bullet warns you during development before users suffer.
π§ Understanding the N+1 Problem
Imagine this simple code:
@posts = Post.all
And in the view:
<% @posts.each do |post| %>
<%= post.user.name %>
<% end %>
Looks innocent, right?
But internally Rails executes:
SELECT * FROM posts;
SELECT * FROM users WHERE id = 1;
SELECT * FROM users WHERE id = 2;
SELECT * FROM users WHERE id = 3;
...
This becomes:
π 1 query for posts π N queries for users
Hence:
β N+1 Query
With 100 posts, Rails may execute 101 queries π€―
π₯ Step 1 β Install Bullet Gem
Add Bullet to your Gemfile:
group :development do
gem 'bullet'
end
Then run:
bundle install
βοΈ Step 2 β Configure Bullet Properly
Open:
config/environments/development.rb
Add:
config.after_initialize do
Bullet.enable = true
Bullet.alert = true
Bullet.bullet_logger = true
Bullet.console = true
Bullet.rails_logger = true
end
π― What Each Configuration Does
ConfigurationPurposeBullet.enableEnables BulletBullet.alertBrowser popup alertsBullet.consoleLogs warnings in browser consoleBullet.bullet_loggerCreates bullet.logBullet.rails_loggerShows warnings in Rails logs
π¨ Step 3 β Detect N+1 Queries
Suppose we have:
class Post < ApplicationRecord
belongs_to :user
end
class User < ApplicationRecord
has_many :posts
end
Controller:
def index
@posts = Post.all
end
View:
<% @posts.each do |post| %>
<%= post.user.name %>
<% end %>
Bullet will immediately warn:
USE eager loading detected
Post => [:user]
Add to your query: .includes([:user])
π₯ Amazing.
β Step 4 β Fix N+1 Queries Correctly
Update controller:
def index
@posts = Post.includes(:user)
end
Now Rails performs:
SELECT * FROM posts;
SELECT * FROM users WHERE id IN (1,2,3,4);
Only 2 queries π
Huge performance boost.
π§ includes vs preload vs eager_load
Many Rails developers misuse these.
Letβs clarify π
1οΈβ£ includes
Most commonly used.
Post.includes(:user)
Rails intelligently decides whether to use:
- Separate queries
- JOIN query
depending on usage.
β Best default choice.
2οΈβ£ preload
Always uses separate queries.
Post.preload(:user)
Useful when JOINs become expensive.
3οΈβ£ eager_load
Always performs LEFT OUTER JOIN.
Post.eager_load(:user)
Useful when filtering/sorting on associated tables.
Example:
Post.eager_load(:user)
.where(users: { active: true })
π Pro Tip β Avoid Overusing includes
Bad:
Post.includes(:user, :comments, :likes, :tags)
Why bad? π¬
Because loading unnecessary associations increases:
β Memory usage β Query complexity β Object allocation
β Best Practice
Only eager load what you actually use.
Good:
Post.includes(:user)
π Step 5 β Detect Unused Eager Loading
Suppose:
@posts = Post.includes(:user)
But view:
<%= post.title %>
You never use user.
Bullet warns:
AVOID eager loading detected
Remove from your query: .includes([:user])
This is INCREDIBLY useful for memory optimization π§
β‘ Step 6 β Counter Cache Optimization
Imagine:
post.comments.count
inside a loop.
Rails fires COUNT query repeatedly π
β Without Counter Cache
SELECT COUNT(*) FROM comments WHERE post_id = ?
again and again.
β Add Counter Cache
Migration:
add_column :posts, :comments_count, :integer, default: 0
Model:
class Comment < ApplicationRecord
belongs_to :post, counter_cache: true
end
Now Rails automatically maintains:
post.comments_count
No extra queries π
π§ͺ Step 7 β Make Bullet Fail Your Tests
Advanced teams do this πͺ
Add in test environment:
Bullet.enable = true
Bullet.raise = true
Now tests fail whenever N+1 queries appear.
This creates a strong performance culture π₯
ποΈ Real-World Example
Imagine an admin dashboard:
@companies = Company.all
View:
<% @companies.each do |company| %>
<%= company.users.count %>
<%= company.threats.count %>
<% end %>
This can generate:
β Hundreds of SQL queries.
β Optimized Version
@companies = Company.includes(:users, :threats)
With counter cache:
company.users_count
company.threats_count
Result:
β‘ Faster dashboard β‘ Lower DB load β‘ Better scalability
π§ Bullet Gem Best Practices
β 1. Enable Only in Development
Never use Bullet in production.
group :development do
gem 'bullet'
end
β 2. Review Every Warning Carefully
Not every warning should blindly be fixed.
Sometimes eager loading increases memory more than query savings.
Optimization is balance βοΈ
β 3. Benchmark Before and After
Use:
rack-mini-profiler
or:
benchmark-ips
to validate improvements.
β 4. Combine with Proper Indexing
Even optimized queries need indexes.
Example:
add_index :comments, :post_id
π§° Amazing Gems to Use Alongside Bullet
GemPurposerack-mini-profilerRequest profilingskylightPerformance monitoringnewrelic_rpmAPM monitoringprosopiteAlternative N+1 detector
π₯ Common Mistakes Developers Make
β Adding includes Everywhere
This causes memory bloat.
β Ignoring Bullet Warnings
Tiny inefficiencies become massive at scale.
β Using count Instead of size
post.comments.count
always hits DB.
Better:
post.comments.size
If association loaded, no query needed π
β‘ Bonus Optimization Trick
Use:
select
to fetch only needed columns.
Instead of:
User.all
use:
User.select(:id, :name)
Less memory. Faster objects.
π― Final Recommended Bullet Configuration
config.after_initialize do
Bullet.enable = true
Bullet.alert = true
Bullet.console = true
Bullet.bullet_logger = true
Bullet.rails_logger = true
Bullet.add_footer = true
end
π Final Thoughts
The Bullet gem is not just a debugging tool.
It teaches you how ActiveRecord actually behaves internally.
Teams that actively use Bullet usually build:
β Faster applications β Scalable architectures β Healthier databases β Better engineering culture
Performance is not something you add later.
Performance is an engineering habit π
And Bullet helps you build that habit every single day.
π Remember
βMake it work, make it right, then make it fast.β
Ruby on Rails gives developer happiness π
Bullet ensures users stay happy too β‘
λ©νλ°μ΄ν°
- post_id
- 76a3a9db1ce0
- slug
- using-bullet-gem-the-right-way-for-maximum-efficiency-in-ruby-on-rails-76a3a9db1ce0
- url
- https://medium.com/@raviskit2012/using-bullet-gem-the-right-way-for-maximum-efficiency-in-ruby-on-rails-76a3a9db1ce0
- canonical_url
- https://medium.com/@raviskit2012/using-bullet-gem-the-right-way-for-maximum-efficiency-in-ruby-on-rails-76a3a9db1ce0
- author_url
- https://medium.com/@raviskit2012
- status
- ok
- fetched_at
- 2026-06-09 15:37:30