Check for N+1 Queries Using Rails Scopes

The Easy Way

Jun 17, 2015 | Bruno Miranda

Checking for N+1 queries isn't necessarily intuitive. Here's one simple way to verify you have eliminated these pesky queries.

This article isn't meant to review all the nuances of includes, joins, or N+1s but to highlight a technique to check a given query.

Take the following scope:

scope :for_auto_suggest, -> {
  includes(:user_verification, :profile_photo_files, { account: [:roles, :career_preference] }, { user_professional_detail: [:specialty] }, { user_practice_contact: [:country] })
}

Because ActiveRecord lazily evaluates queries, running the following on console doesn't necessarily highlight the N+1 problem:

User.for_auto_suggest.limit(3)

However, you can force it to show you the N+1 by requesting the joined attribute you're after:

User.for_auto_suggest.limit(3).map(&:city)

At this point you'll see something that looks suspicious:

City Load (0.4ms)  SELECT  `cities`.* FROM `cities`  WHERE `cities`.`id` = 127817 LIMIT 1
  app/models/user.rb:449:in `city_name'
  bin/rails:8:in `<top (required)>'
City Load (0.3ms)  SELECT  `cities`.* FROM `cities`  WHERE `cities`.`id` = 102319 LIMIT 1
  app/models/user.rb:449:in `city_name'
  bin/rails:8:in `<top (required)>'
City Load (0.2ms)  SELECT  `cities`.* FROM `cities`  WHERE `cities`.`id` = 127561 LIMIT 1
  app/models/user.rb:449:in `city_name'
  bin/rails:8:in `<top (required)>'

By modifying your scope to include the associated model :city in the correct nested association...:

{ user_practice_contact: [:country, :city ] }

...you can re-run the same scope and the log will look more like this:

City Load (0.3ms)  SELECT `cities`.* FROM `cities`  WHERE `cities`.`id` IN (127817, 102319, 127561)
  bin/rails:8:in `<top (required)>'

This is exactly what you want to see.