In this article we will be explaining the difference between includes and joins in Rails ActiveRecord, a topic which often confuses newbies to Rails.
Join
Let’s look at two models, Booking and User. In the User model we have:
1
has_many :bookings
Whereas in the Booking model we have:
1
belongs_to :user
Let’s suppose we already have some data in the database and we have the following code:
1
2
3
4
bookings = Booking.where(status: ‘completed’).joins(:user)
bookings.each do |booking|
puts booking.user.name
end
This will fire n+1 queries:
1
2
3
4
5
6
7
8
Booking Load (0.3ms) SELECT "bookings".* FROM "bookings"
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 2], ["LIMIT", 1]]
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 2], ["LIMIT", 1]]
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 2], ["LIMIT", 1]]
Includes
The n+1 queries problem can be resolved by using includes.
1
2
3
4
5
bookings = Booking.includes(:user)
bookings.each do |booking|
puts booking.user.name
end
This will fire only two queries:
1
2
Booking Load (4.2ms) SELECT "bookings".* FROM "bookings"
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" IN ($1, $2) [["id", 1], ["id", 2]]
However, includes cannot be used in every instance. When you have to filter results from the join table, for example to get all bookings for a specific user, you cannot avoid joins.
1
2
3
4
5
bookings = Booking.joins(:user).where(email: ‘example@email.com’)
bookings.each do |booking|
puts booking.user.name
end
Happy joining and including!