Chapter 05 · Connect & Compute
5Relationships & Derived Data
Records rarely stand alone. Ash lets you connect them, then compute new values from them — without hand-writing the SQL.
Relationships: how records connect
A ticket belongs to a representative; a representative has many tickets. You declare both sides, and Ash understands the graph:
on Ticketrelationships do belongs_to :representative, Helpdesk.Support.Representative end
on Representativerelationships do has_many :tickets, Helpdesk.Support.Ticket end
The four kinds cover almost everything:
| Relationship | Meaning |
|---|---|
belongs_to | This record points at one other (holds the foreign key). |
has_many | Many others point back at this one. |
has_one | Exactly one other points back at this one. |
many_to_many | Both sides have many, linked through a join. |
To fetch related data, you load it — nothing is fetched until you ask, so you never accidentally drag the whole graph along:
Helpdesk.Support.get_ticket_by_id!(id, load: [:representative]) # load the other direction, too rep = Ash.load!(rep, :tickets)
No N+1 by accident
Loading is explicit and batched. Ask for
load: [:tickets] across
a list of reps and Ash fetches them together — the classic “N+1 queries”
trap is designed out of the common path.
Derived data: don't store what you can compute
Some values aren't facts you store — they're answers you compute. Ash has two first-class tools for them.
Calculations — per-record computed fields
A calculation derives a value for a single record, on demand:
on Ticketcalculations do calculate :is_open, :boolean, expr(status == :open) end
Aggregates — summaries across a relationship
An aggregate rolls up related records into one number or fact:
on Representativeaggregates do count :open_ticket_count, :tickets do filter expr(status == :open) end end
Why this is a big deal
Because calculations and aggregates are declared on the resource,
the data layer can push them down into a single efficient SQL query, they can
be filtered and sorted on, and they show up in your APIs — all from
one line. You describe the answer; Ash figures out the query.