Active Record provides many ways in which we can load the associations of any row, namely: preload, eager_load, includes, and joins.
Preload:
preload loads all the associations in a separate query.
Suppose we have something like bank_account
and investor
, bank_account belongs to a investor. When we do:
BankAccount.preload(:investor).last
# This will fire two queries,
# BankAccount Load (1.6ms) SELECT "bank_accounts".* FROM "bank_accounts" ORDER BY "bank_accounts"."id" DESC LIMIT 1
# Investor Load (1.0ms) SELECT "investors".* FROM "investors" WHERE "investors"."id" IN (122296) ORDER BY "investors"."created_at" DESC
preload
can't be used for data filtering as there are two separate queries required.
Something like the below query will fail.
BankAccount.preload(:investor).where("investors.id = 1").last
# error is thrown
# ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause entry for table "investors"
Includes:
includes
is a beautiful feature provided by active record. Rails is smart enough to decides which strategy to use for you. With includes
, you tell the ORM what to do, instead of how to do.
Something like:
BankAccount.includes(:investor).where(investors: { id: 15 }).last # fires 1 common query
BankAccount.includes(:investor).last # fires 2 different queries, 1 for banks and 1 for investors
BankAccount.includes(:investor).references(:investor).last # force includes to fire a single query
includes
uses LEFT OUTER JOIN
to load the data.
Eager Load:
eager_load
does exactly what includes
does if it has to load the results in a single query.
BankAccount.eager_load(:investor).last
# uses LEFT OUTER JOIN in order to load the association.
Joins:
joins
uses inner join
by default.
So suppose if we do, Investor.joins(:bank_account).count
, it will give us all the investors which have at least one bank account. There may be duplicate entries for the investors here.
Investor.joins(:bank_account).pluck(:id).count # result 1
Investor.joins(:bank_account).pluck(:id).uniq.count # result 2
The interesting part here is, joins
doesn't help with n+1
problem. It is just used for filtering
while joining
two tables. Eg. Investor.joins(:bank_account).where(bank_accounts: { account_number: 30824344045 } ).count
If we use investor.bank_accounts
after the query above, it will fire another query to load the result for that.