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.