Eager Load A Rails Association Only When It Exists To Avoid N1
includes(:profile) on a has_one loads a row per record even when most records have no profile. Filtering to the records that actually have the association, or loading it conditionally, keeps the query cheap without reintroducing N+1.
Description
Use where.associated(:profile) (Rails 7+) for “only records that have one” — it builds an INNER JOIN. where.missing(:profile) gives you the opposite via a LEFT JOIN with a NULL check, which is the clean replacement for a hand-written LEFT OUTER JOIN ... IS NULL.
When you need all records but only some have the association, preload is the right tool: it issues one extra query for the associations that exist, and leaves the rest nil. It never joins, so it can’t drop rows.
Avoid conditional eager loading based on a Ruby-side check — that’s where N+1 comes back.
Sample input:
User.where.associated(:profile)
User.where.missing(:profile)
Sample Output:
SELECT users.* FROM users INNER JOIN profiles ON profiles.user_id = users.id
SELECT users.* FROM users LEFT OUTER JOIN profiles ON profiles.user_id = users.id
WHERE profiles.id IS NULL
Answer
# Only records that HAVE the association (Rails 7+)
User.where.associated(:profile)
# Only records that DON'T
User.where.missing(:profile)
# All records, association preloaded where present — two queries, no joins
users = User.preload(:profile)
users.each { |u| u.profile&.city } # no extra queries
# Conditional preload based on what the page needs
scope = User.all
scope = scope.preload(:profile) if params[:include_profile]
# Preload a filtered association without breaking the parent query
class User < ApplicationRecord
has_many :orders
has_many :recent_orders,
-> { where(created_at: 30.days.ago..) },
class_name: "Order"
end
User.preload(:recent_orders)
# Filter on the association AND load it — needs eager_load, not preload
User.eager_load(:profile).where(profiles: { country: "NP" })
Check viewARU - Brand Newsletter!
Newsletter to DEVs by DEVs - boost your Personal Brand & career! 🚀