Relations
User.where(...), User.order(...), and friends return a chainable relation
that defers running SQL until you ask for results. Relations compose: every
scope-narrowing call returns a new relation, leaving the original untouched.
1 2 3 4 5 | |
Realising a relation
Relations stay lazy until you call one of these methods:
| Method | Returns |
|---|---|
.all |
List of model instances |
.first |
One instance ordered by id ASC, or Nil |
.last |
One instance ordered by id DESC, or Nil |
.count |
Int — COUNT(*) |
.exists |
Bool |
.pluck(...) |
List of raw column values |
.ids |
List of id column values (= pluck('id')) |
where
where(%conditions) adds equality conditions joined with AND. Conditions
are bound as parameters, never interpolated, so user-supplied values are safe.
1 | |
You can chain further .where(...) calls to merge in additional conditions.
1 2 3 | |
where accepts several value shorthands beyond a literal scalar:
1 2 3 4 | |
See Queries for the full filtering vocabulary, including
where.not, where.missing, where.associated, or, and, merge,
rewhere, unscope, and excluding.
order
order(*@cols) adds ORDER BY clauses. Pass column names or fully formed
fragments like 'fname DESC'.
1 2 3 | |
reorder(...) replaces any prior order clauses. in-order-of(:col, [...])
orders rows to match an explicit value list.
1 2 | |
limit and offset
limit(N) and offset(N) add LIMIT and OFFSET. Useful for pagination.
1 2 3 | |
SQLite and MySQL require a LIMIT whenever an OFFSET is set; the adapter
adds a synthetic unbounded LIMIT when you pass offset alone.
all
Model.all returns a relation that, once realised, returns every row. You can
chain conditions onto it just like where.
1 | |
none
Model.none returns a chainable null relation. Every operation that would
hit the database returns the empty result for its return type ([], 0,
False, Nil, …) without issuing SQL. Useful as a "no match" return value
from helper methods that must still hand back a chainable relation.
1 2 3 4 5 6 7 | |
none is sticky once set; further where, order, etc. compose but the
result stays empty. merge(other.none) propagates the null relation.
pluck and ids
pluck returns raw column values without instantiating model objects. It is
much cheaper than materialising records and dropping everything but one
column.
1 2 3 4 5 | |
ids is the common shorthand for pluck('id').
1 | |
count and exists
count returns the number of matching rows. exists returns True if any
row matches.
1 2 | |
preload, eager-load, includes
These three modifiers eliminate the N+1 query problem by loading associations up front and caching them on each parent record.
preload(...) runs one extra query per named association after fetching the
parent rows. It is the right default when you only need to read the children
back through the accessor.
1 2 3 4 | |
eager-load(...) does the same caching but also adds a LEFT OUTER JOIN to the
parent query. Use this when you need to filter on a joined column:
1 | |
includes(...) behaves like preload by default. It promotes itself to
eager-load when something later in the chain proves a JOIN is required:
- An explicit
references(:assoc)— order of the two calls doesn't matter. - A
where/order/havingfragment that mentions a column on the joined table, in any of the supported forms.
1 2 3 4 5 6 | |
references takes the colon-pair form references(:profile) or the
arrow-pair form references(profile => True); a falsy value
(references(profile => False)) is ignored, so it adds nothing and does not
promote includes.
preload(:assoc) and eager-load(:assoc) are explicit and never get
re-routed: a chain like preload(:profile).references(:profile) stays a
preload, and eager-load(:profile) stays a JOIN even with no references.
Both forms Rails uses for nested includes are supported, and the three
loaders (preload, eager-load, includes) accept the same shapes.
Array form — multiple top-level associations:
1 2 | |
Hash form (Raku Pair) — load a child association on top of its parent:
1 2 | |
The two forms compose, including for arbitrary depth. The value side of a
Pair can itself be another Pair, a Hash, or a list:
1 2 3 4 5 | |
Each loaded record exposes its cache as record.assoc-cache<name>, so tests
and instrumentation can verify what was preloaded without re-querying.
Polymorphic associations
preload and includes both batch polymorphic associations by type:
1 2 3 4 5 6 7 8 | |
eager-load(:polymorphic-assoc) raises an error — the target table varies
per row, so a single LEFT OUTER JOIN cannot resolve it. Use preload (or
includes without a JOIN-forcing chain) instead.
The polymorphic inverse (has_many :as) is loaded with a single query
scoped by <as>_id and <as>_type:
1 2 3 4 5 6 7 8 | |
Nested preloads from a polymorphic belongs-to work even though the parents
returned have different classes. The preloader partitions the cached parents
by class before recursing, and silently skips classes that do not declare the
named child association:
1 | |
has_many / has_one :through
Through-associations are loaded in two batched steps: first the intermediate
collection on the parents, then the source association on the intermediates.
The final cache on each parent is the flattened (has_many) or singular
(has_one) source value.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
eager-load(:through-assoc) joins through the intermediate table to the
source. With has_many :through, the JOIN inflates the result set with one
row per join row — pair it with distinct (or fall back to preload) when
you only need the parents.