See all articles

How ActiveRecord decides when to preload and when eager load records

Paweł Dąbrowski-Chief Technology Officer

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:

1 2 3 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:

1 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:

1 2 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:

1 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:

1 2 3 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:

1 2 3 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!

Similar articles