Need help with bullet?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

6.4K Stars 366 Forks MIT License 1.3K Commits 109 Opened issues


help to kill N+1 queries and unused eager loading

Services available


Need anything else?

Contributors list


Main workflow Gem Version AwesomeCode Status for flyerhzm/bullet Coderwall Endorse

The Bullet gem is designed to help you increase your application's performance by reducing the number of queries it makes. It will watch your queries while you develop your application and notify you when you should add eager loading (N+1 queries), when you're using eager loading that isn't necessary and when you should use counter cache.

Best practice is to use Bullet in development mode or custom mode (staging, profile, etc.). The last thing you want is your clients getting alerts about how lazy you are.

Bullet gem now supports activerecord >= 4.0 and mongoid >= 4.0.

If you use activerecord 2.x, please use bullet <= 4.5.0

If you use activerecord 3.x, please use bullet < 5.5.0

External Introduction


You can install it as a gem:

gem install bullet

or add it into a Gemfile (Bundler):

gem 'bullet', group: 'development'

enable the Bullet gem with generate command

bundle exec rails g bullet:install

The generate command will auto generate the default configuration and may ask to include in the test environment as well. See below for custom configuration.

Note: make sure

gem is added after activerecord (rails) and mongoid.


Bullet won't do ANYTHING unless you tell it to explicitly. Append to

initializer with the following code:
config.after_initialize do
  Bullet.enable = true
  Bullet.sentry = true
  Bullet.alert = true
  Bullet.bullet_logger = true
  Bullet.console = true
  Bullet.growl = true
  Bullet.xmpp = { :account  => '[email protected]',
                  :password => 'bullets_password_for_jabber',
                  :receiver => '[email protected]',
                  :show_online_status => true }
  Bullet.rails_logger = true
  Bullet.honeybadger = true
  Bullet.bugsnag = true
  Bullet.appsignal = true
  Bullet.airbrake = true
  Bullet.rollbar = true
  Bullet.add_footer = true
  Bullet.skip_html_injection = false
  Bullet.stacktrace_includes = [ 'your_gem', 'your_middleware' ]
  Bullet.stacktrace_excludes = [ 'their_gem', 'their_middleware', ['my_file.rb', 'my_method'], ['my_file.rb', 16..20] ]
  Bullet.slack = { webhook_url: 'http://some.slack.url', channel: '#default', username: 'notifier' }

The notifier of Bullet is a wrap of uniform_notifier

The code above will enable all of the Bullet notification systems: *

: enable Bullet gem, otherwise do nothing *
: pop up a JavaScript alert in the browser *
: log to the Bullet log file (Rails.root/log/bullet.log) *
: log warnings to your browser's console.log (Safari/Webkit browsers or Firefox w/Firebug installed) *
: pop up Growl warnings if your system has Growl installed. Requires a little bit of configuration *
: send XMPP/Jabber notifications to the receiver indicated. Note that the code will currently not handle the adding of contacts, so you will need to make both accounts indicated know each other manually before you will receive any notifications. If you restart the development server frequently, the 'coming online' sound for the Bullet account may start to annoy - in this case set :showonlinestatus to false; you will still get notifications, but the Bullet account won't announce it's online status anymore. *
: add warnings directly to the Rails log *
: add notifications to Honeybadger *
: add notifications to bugsnag *
: add notifications to airbrake *
: add notifications to AppSignal *
: add notifications to rollbar *
: add notifications to sentry *
: adds the details in the bottom left corner of the page. Double click the footer or use close button to hide footer. *
: prevents Bullet from injecting code into the returned HTML. This must be false for receiving alerts, showing the footer or console logging. *
: don't add headers to API requests, and remove the javascript that relies on them. Note that this prevents bullet from logging warnings to the browser console or updating the footer. *
: include paths with any of these substrings in the stack trace, even if they are not in your main app *
: ignore paths with any of these substrings in the stack trace, even if they are not in your main app. Each item can be a string (match substring), a regex, or an array where the first item is a path to match, and the second item is a line number, a Range of line numbers, or a (bare) method name, to exclude only particular lines in a file. *
: add notifications to slack *
: raise errors, useful for making your specs fail unless they have optimized queries

Bullet also allows you to disable any of its detectors.

# Each of these settings defaults to true

Detect N+1 queries

Bullet.n_plus_one_query_enable = false

Detect eager-loaded associations which are not used

Bullet.unused_eager_loading_enable = false

Detect unnecessary COUNT queries which could be avoided

with a counter_cache

Bullet.counter_cache_enable = false

Safe list

Sometimes Bullet may notify you of query problems you don't care to fix, or which come from outside your code. You can add them to a safe list to ignore them:

Bullet.add_safelist :type => :n_plus_one_query, :class_name => "Post", :association => :comments
Bullet.add_safelist :type => :unused_eager_loading, :class_name => "Post", :association => :comments
Bullet.add_safelist :type => :counter_cache, :class_name => "Country", :association => :cities

If you want to skip bullet in some specific controller actions, you can do like

class ApplicationController < ActionController::Base
  around_action :skip_bullet, if: -> { defined?(Bullet) }

def skip_bullet previous_value = Bullet.enable? Bullet.enable = false yield ensure Bullet.enable = previous_value end end


The Bullet log

will look something like this:
  • N+1 Query:
2009-08-25 20:40:17[INFO] N+1 Query: PATH_INFO: /posts;    model: Post => associations: [comments]·
Add to your finder: :include => [:comments]
2009-08-25 20:40:17[INFO] N+1 Query: method call stack:·
/Users/richard/Downloads/test/app/views/posts/index.html.erb:11:in `_run_erb_app47views47posts47index46html46erb'
/Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `each'
/Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `_run_erb_app47views47posts47index46html46erb'
/Users/richard/Downloads/test/app/controllers/posts_controller.rb:7:in `index'

The first two lines are notifications that N+1 queries have been encountered. The remaining lines are stack traces so you can find exactly where the queries were invoked in your code, and fix them.

  • Unused eager loading:
2009-08-25 20:53:56[INFO] Unused eager loadings: PATH_INFO: /posts;    model: Post => associations: [comments]·
Remove from your finder: :include => [:comments]

These two lines are notifications that unused eager loadings have been encountered.

  • Need counter cache:
2009-09-11 09:46:50[INFO] Need Counter Cache
  Post => [:comments]

Growl, XMPP/Jabber and Airbrake Support



If you find Bullet does not work for you, please disable your browser's cache.


Work with ActiveJob


in your
class ApplicationJob < ActiveJob::Base
  include Bullet::ActiveJob if Rails.env.development?

Work with other background job solution

Use the Bullet.profile method.

class ApplicationJob < ActiveJob::Base
  around_perform do |_job, block|
    Bullet.profile do

Work with sinatra

Configure and use

configure :development do
  Bullet.enable = true
  Bullet.bullet_logger = true
  use Bullet::Rack

Run in tests

First you need to enable Bullet in test environment.

# config/environments/test.rb
config.after_initialize do
  Bullet.enable = true
  Bullet.bullet_logger = true
  Bullet.raise = true # raise an error if n+1 query occurs

Then wrap each test in Bullet api.

# spec/rails_helper.rb
if Bullet.enable?
  config.before(:each) do

config.after(:each) do Bullet.perform_out_of_channel_notifications if Bullet.notification? Bullet.end_request end end

Debug Mode

Bullet outputs some details info, to enable debug mode, set




Bullet is designed to function as you browse through your application in development. To see it in action, you can visit or follow these steps to create, detect, and fix example query problems.

1. Create an example application

$ rails new test_bullet
$ cd test_bullet
$ rails g scaffold post name:string
$ rails g scaffold comment name:string post_id:integer
$ bundle exec rake db:migrate

2. Change

class Post < ActiveRecord::Base
  has_many :comments

class Comment < ActiveRecord::Base belongs_to :post end

3. Go to

rails c
and execute
post1 = Post.create(:name => 'first')
post2 = Post.create(:name => 'second')
post1.comments.create(:name => 'first')
post1.comments.create(:name => 'second')
post2.comments.create(:name => 'third')
post2.comments.create(:name => 'fourth')

4. Change the

to produce a N+1 query
     'Are you sure?', :method => :delete %>

5. Add the

gem to the
gem "bullet"

And run

bundle install

6. enable the Bullet gem with generate command

bundle exec rails g bullet:install

7. Start the server

$ rails s

8. Visit

in browser, and you will see a popup alert box that says
The request has unused preload associations as follows:
The request has N+1 queries as follows:
model: Post => associations: [comment]

which means there is a N+1 query from the Post object to its Comment association.

In the meantime, there's a log appended into

2010-03-07 14:12:18[INFO] N+1 Query in /posts
  Post => [:comments]
  Add to your finder: :include => [:comments]
2010-03-07 14:12:18[INFO] N+1 Query method call stack
  /home/flyerhzm/Downloads/test_bullet/app/views/posts/index.html.erb:14:in `_render_template__600522146_80203160_0'
  /home/flyerhzm/Downloads/test_bullet/app/views/posts/index.html.erb:11:in `each'
  /home/flyerhzm/Downloads/test_bullet/app/views/posts/index.html.erb:11:in `_render_template__600522146_80203160_0'
  /home/flyerhzm/Downloads/test_bullet/app/controllers/posts_controller.rb:7:in `index'

The generated SQL is:

Post Load (1.0ms)   SELECT * FROM "posts"
Comment Load (0.4ms)   SELECT * FROM "comments" WHERE ("comments".post_id = 1)
Comment Load (0.3ms)   SELECT * FROM "comments" WHERE ("comments".post_id = 2)

9. To fix the N+1 query, change

def index
  @posts = Post.includes(:comments)

respond_to do |format| format.html # index.html.erb format.xml { render :xml => @posts } end end

10. Refresh

. Now there's no alert box and nothing new in the log.

The generated SQL is:

Post Load (0.5ms)   SELECT * FROM "posts"
Comment Load (0.5ms)   SELECT "comments".* FROM "comments" WHERE ("comments".post_id IN (1,2))

N+1 query fixed. Cool!

11. Now simulate unused eager loading. Change

def index
  @posts = Post.includes(:comments)

respond_to do |format| format.html # index.html.erb format.xml { render :xml => @posts } end end

     'Are you sure?', :method => :delete %>

12. Refresh

, and you will see a popup alert box that says
The request has unused preload associations as follows:
model: Post => associations: [comment]
The request has N+1 queries as follows:

Meanwhile, there's a line appended to

2009-08-25 21:13:22[INFO] Unused preload associations: PATH_INFO: /posts;    model: Post => associations: [comments]·
Remove from your finder: :include => [:comments]

13. Simulate countercache. Change `app/controllers/postscontroller.rb

def index
  @posts = Post.all

respond_to do |format| format.html # index.html.erb format.xml { render :xml => @posts } end end

     'Are you sure?', :method => :delete %>

14. Refresh

, then you will see a popup alert box that says
Need counter cache
  Post => [:comments]

Meanwhile, there's a line appended to

2009-09-11 10:07:10[INFO] Need Counter Cache
  Post => [:comments]

Copyright (c) 2009 - 2019 Richard Huang ([email protected]), released under the MIT license

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.