Ash Framework · Basics Contents Cheatsheet

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:

RelationshipMeaning
belongs_toThis record points at one other (holds the foreign key).
has_manyMany others point back at this one.
has_oneExactly one other points back at this one.
many_to_manyBoth 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.
Representative Dana Ticket · open Ticket · open Ticket · closed has_many :tickets open_ticket_count → 2
An aggregate turns “count Dana's open tickets” into one declared, query-pushed field.
⌂ Library