Kalpit
Kalpit Full stack developer working on Ruby on Rails and React

Eager loading in rails

Eager loading in rails

Eager loading is nothing but loading all the associated records using as few queries as possible.

Most of us heard about N + 1 query problem.

Let’s assume we have 2 models as User and Company. As association as follows

1
2
3
4
5
# models/user.rb

class User < ApplicationRecord
	has_one :company
end
1
2
3
4
# models/comapny.rb
class Company < ApplicationRecord
	belongs_to :user
end

Consider the following code, which finds all the users and prints the company name

1
2
3
4
5
users = User.all

users.each do |user|
	puts user.company.name
end

Above code looks fine and in working condition. But we have problem of N + 1 query here.

The above code executes 1 (find all users) + N (one per each user to load company) = N + 1 queries.

Console output for above queries:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
3.0.3 :006 > users = User.all
  User Load (0.6ms)  SELECT "users".* FROM "users"
 =>                                                                
[#<User:0x0000000115644b30                                         
...                                                                
3.0.3 :007 > users.each do |user|
3.0.3 :008 >   puts user.company.name
3.0.3 :009 > end
  Company Load (0.7ms)  SELECT "companies".* FROM "companies" WHERE "companies"."user_id" = $1 LIMIT $2  [["user_id", 1], ["LIMIT", 1]]
Pres. Timmy Mayer                                                                         
  Company Load (0.7ms)  SELECT "companies".* FROM "companies" WHERE "companies"."user_id" = $1 LIMIT $2  [["user_id", 2], ["LIMIT", 1]]
Li Weber                                                                                  
  Company Load (0.2ms)  SELECT "companies".* FROM "companies" WHERE "companies"."user_id" = $1 LIMIT $2  [["user_id", 3], ["LIMIT", 1]]
Emmett Dicki                                                                              
  Company Load (0.1ms)  SELECT "companies".* FROM "companies" WHERE "companies"."user_id" = $1 LIMIT $2  [["user_id", 4], ["LIMIT", 1]]
Jessie Johnson LLD                                                                        
  Company Load (0.2ms)  SELECT "companies".* FROM "companies" WHERE "companies"."user_id" = $1 LIMIT $2  [["user_id", 5], ["LIMIT", 1]]
Jesus Ankunding                                                                           
  Company Load (0.1ms)  SELECT "companies".* FROM "companies" WHERE "companies"."user_id" = $1 LIMIT $2  [["user_id", 6], ["LIMIT", 1]]
Oswaldo Kris                                                                              
  Company Load (0.1ms)  SELECT "companies".* FROM "companies" WHERE "companies"."user_id" = $1 LIMIT $2  [["user_id", 7], ["LIMIT", 1]]
Miss Rich Reynolds                                                                        
  Company Load (0.1ms)  SELECT "companies".* FROM "companies" WHERE "companies"."user_id" = $1 LIMIT $2  [["user_id", 8], ["LIMIT", 1]]
The Hon. Felipe Casper
  Company Load (0.1ms)  SELECT "companies".* FROM "companies" WHERE "companies"."user_id" = $1 LIMIT $2  [["user_id", 9], ["LIMIT", 1]]
Stan Willms
  Company Load (0.1ms)  SELECT "companies".* FROM "companies" WHERE "companies"."user_id" = $1 LIMIT $2  [["user_id", 10], ["LIMIT", 1]]
Gov. Alleen Murray

Solution to N + 1 queries problem:

We can use .includes() in order to solve N + 1 query problem.

The includes method eager loads all the associated records. Hence, no need to load associated records every time. No need to call or execute extra query each time when we call associated record.

Using includes in above code:

1
2
3
4
5
users = User.incldues(:comapny)

users.each do |user|
	puts user.comapny.name
end

Output for above code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
3.0.3 :010 > users = User.includes(:company)
  User Load (1.0ms)  SELECT "users".* FROM "users"
  Company Load (1.2ms)  SELECT "companies".* FROM "companies" WHERE "companies"."user_id" IN ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)  [["user_id", 1], ["user_id", 2], ["user_id", 3], ["user_id", 4], ["user_id", 5], ["user_id", 6], ["user_id", 7], ["user_id", 8], ["user_id", 9], ["user_id", 10]]
 =>                                                                                           
[#<User:0x00000001148cd5b0                                                                    
...                                                                                           
3.0.3 :011 > user.each do |user|
3.0.3 :012 >   puts user.company.name
3.0.3 :013 > end
Pres. Timmy Mayer
Li Weber                                                                                      
Emmett Dicki                                                                                  
Jessie Johnson LLD                                                                            
Jesus Ankunding                                                                               
Oswaldo Kris                                                                                  
Miss Rich Reynolds
The Hon. Felipe Casper
Stan Willms
Gov. Alleen Murray

Note that only 2 queries are executed when we used includes method. In this way we can optimise performance of our rails application. 🥂

Stay tuned for difference between incldues, preload and eager_load.

comments powered by Disqus