
How ActiveRecord decides when to preload and when eager load records

Which two methods do Active Record use under the hood of the includes method? Only a small percentage of the developers participating in our Mortal Coding programming tournament knew the answer to this question. Therefore we decided to write an article explaining this issue. What’s the correct answer? And why is it important in daily development?
Which two methods do Active Record use under the hood of the includes method?
a. preload
and eager_load
b. preload
and joins
c. references
and eager_load
d. joins
and eager_load
The correct answer is a. Unfortunately, only 34% of developers answered correctly. Therefore, we decided to write an article explaining this topic and the correct answer because this knowledge is valuable in daily development.
Curious how Active Record handles includes
method under the hood? Let’s see.
What is preloading in Active Record
As the name states, Active Record can pre-load records from the given association. Such a process is beneficial when you want to access the association’s records from the view.
With preload, Rails will always generate two separated SQL queries:
User.preload(:tests)
# SELECT "users".* FROM "users"
# SELECT "tests".* FROM "tests" WHERE "tests"."user_id" IN ($1, $2, $3, $4, $5)
You can now access tests for the given user without calling the database again. However, when using preload
, you can’t refer to the association in the query. For example, the following code will throw an error:
User.preload(:tests).where(tests: { status: :added })
It happens because we load the data in two separated queries; it’s not a join type of query.
What is eager loading in Active Record
Eager loading solves the error raised when using preload and referring to the association in the query. It solves this problem because Rails uses the JOIN clause (left outer join to be more specific) to connect the data:
User.eager_load(:tests)
# SELECT "users"."id" AS t0_r0, "users"."email" AS t0_r1, ... FROM "users" LEFT OUTER JOIN "tests" ON "tests"."user_id" = "users"."id"
We can now easily update our query and refer to the tests table without getting an error:
User.eager_load(:tests).where(tests: { status: :added })
Use eager load if you don’t want to load all associated records and you want to narrow the result using some type of criteria on the query level.
How includes is working in Active Record
Includes connects those two methods, eager_load
and preload
that we discussed before. By the default, it works like preload, so those two invocations produce the same SQL query:
User.preload(:tests)
# is the same as
User.includes(:tests)
However, when you would refer to the included association in the query, includes will use eager_load
to make it possible:
User.eager_load(:tests).where(tests: { status: :added })
# is the same as
User.includes(:tests).where(tests: { status: :added })
If you use includes
, Rails choose for you, but nothing prevents you from making a decision for yourself and deciding whether to use eager_load
or preload
.
The right answer
The includes
method from the Active Record library decides whether to use the preload
method to load data in two separated queries or use eager_load
to load the data in one query with a left outer join.
Soon we will publish more articles regarding the hardest questions from the Mortal Coding challenge. Stay tuned!
If you are looking for an experienced development Ruby on Rails team, let’s talk about how we can help you empower your teams with top-notch engineers!

Chief Technology Officer
Open source fan and growth seeker with over a decade of experience in writing for both human beings and computers. I connect the dots to create high-quality software, valuable relations with people and businesses.

