Kick far away N+1 from your Rails App (Part 2)

Hammam Firdaus
4 min readMay 7, 2023

--

Photo by Joel Muniz on Unsplash

By the way, previous article we talk about N+1 in Rails App for retrieving and inserting data. So, in this article, we will go to talk about How to prevent N+1 or How to know, what are your Rails App is N+1 free

Bullet (helps to kill N+1 queries and unused eager loading)

They said 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.

That was very cool, here I will show you how to configure Rails App with Gem and I will give an example too 👌

Setup the Stuff

Actually, I just need Bullet for development and Test environment. So, I put to group :development, :test only

group :development, :test do
# helps to kill N+1 queries and unused eager loading
gem 'bullet'
end

after gem is installed in your rails app, you can run

bundle exec rails g bullet:install

So, in config/environments/development.rb and config/environments/test.rb you will get code from Bullet like this.

# config/environments/development.rb
config.after_initialize do
Bullet.enable = true # enable Bullet gem, otherwise do nothing
Bullet.alert = true # pop up a JavaScript alert in the browser
Bullet.bullet_logger = true # log to the Bullet log file (Rails.root/log/bullet.log)
Bullet.console = true # log warnings to your browser's console.log (Safari/Webkit browsers or Firefox w/Firebug installed)
Bullet.rails_logger = true # add warnings directly to the Rails log
Bullet.add_footer = true # adds the details in the bottom left corner of the page. Double click the footer or use close button to hide footer.
end

# config/environments/test.rb
config.after_initialize do
Bullet.enable = true # enable Bullet gem, otherwise do nothing
Bullet.bullet_logger = true # log to the Bullet log file (Rails.root/log/bullet.log)
Bullet.raise = true # raise an error if n+1 query occurs
end

Let’s try!

Test environment

After everything is ready, we can try the utility from Bullet. So, I will show you the test environment first with the unit test (I use rspec).

Here I have some code examples.

# controller
class UserController < ApplicationController
before_action :authenticate_user!
layout 'dashboard'

def index
@pagy, @users = pagy(User.where(organization_id: current_user.organization_id))
end
end
<!-- <tbody> -->
<% @users.each do |user| %> <tr>
<td class="px-4 py-4 text-sm font-medium whitespace-nowrap">
<div>
<h2 class="font-medium text-gray-800 dark:text-white "><%= user.name %> </h2>
<!-- WATCH HERE -->
<p class="text-sm font-normal text-gray-600 dark:text-gray-400"><%= user.organization.name %></p>
<!-- HERE -->
</div>
</td>
<% end %>
<!-- </tbody> -->

and here is the unit test

# frozen_string_literal: true
require 'rails_helper'
RSpec.describe "Users", type: :request do
include Warden::Test::Helpers
let(:model_organization) { create :organization }

def sign_in(resource_or_scope, resource = nil)
resource ||= resource_or_scope
scope = Devise::Mapping.find_scope!(resource_or_scope)
login_as(resource, scope: scope)
end

before do
create_list(:user, 100, organization: model_organization)
end
it 'should be true when get list user' do
sign_in create(:user, organization: model_organization)
get '/user/index'
expect(response.status).to eq(200)
end
end

and here are the results

❯ bundle exec rspec test/controllers/user_controller_rspec.rb
F
Failures:

1) Users should be true when get list user
Failure/Error: get '/user/index'

Bullet::Notification::UnoptimizedQueryError:
user: mamxalf
GET /user/index
USE eager loading detected
User => [:organization]
Add to your query: .includes([:organization])
Call stack
/Users/mamxalf/Repository/Project/Conto/Conto/app/views/user/index.html.erb:42:in `block in _app_views_user_index_html_erb__3089862282655144503_31580'
/Users/mamxalf/Repository/Project/Conto/Conto/app/views/user/index.html.erb:37:in `_app_views_user_index_html_erb__3089862282655144503_31580'
/Users/mamxalf/Repository/Project/Conto/Conto/test/controllers/user_controller_rspec.rb:21:in `block (2 levels) in <top (required)>'
# ./test/controllers/user_controller_rspec.rb:21:in `block (2 levels) in <top (required)>'Finished in 0.57007 seconds (files took 1.65 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./test/controllers/user_controller_rspec.rb:19 # Users should be true when get list user
~/Repository/Project/Conto/Conto expreiment* 07:16:49 AM

The bullet will rise the error, and how to fix it? Of course, you should go to the previous article in part 1 🤣.

If you has been fix it, here are the results.

❯ bundle exec rspec test/controllers/user_controller_rspec.rb
.
Finished in 0.49415 seconds (files took 1.64 seconds to load)
1 example, 0 failures
~/Repository/Project/Conto/Conto expreiment* 07:48:54 AM

Yeaayy Bullet did not raise the N+1 warning again

Development Environment

Because I’m setup in development too. So, let me show you if in development any N+1 errors what it looks like?

Go to the router from user_controller#index and you will see an alert like this. and you can change the config in config/environment/development.rb so, you can change the alert place.

Error in browser (This is Safari Browser)

How to fix it? Of course, you should go to the previous article in part 1 🤣.

--

--

No responses yet