Overview
- Use JOINs + nested selection for author pages, listings with badges, and catalogs where denormalized snippets from related entities improve recall/UX
- Add grouping to show a single representative per logical key (e.g., one book per author, one variant per product)
- Trade‑offs:
- Payload size vs clarity: include only fields you render; nested selection reduces bloat
- Grouping changes pagination semantics (per_page applies to groups, not hits)
- Sorting applies before grouping; within‑group order follows backend order
Association DSL & joins
- Declare associations on the model:
belongs_to :authorandhas_many :books(overridecollection:,local_key:,foreign_key:when needed). - Select associations on the relation with
joins(:association); names are validated against the model registry - Nested associations: only single‑hop paths are supported (
$assoc.field); multi‑hop is not supported - Keys auto‑resolve by default (see Joins → Auto‑resolution). Override for non‑standard schemas.
- Supported join usage: selection, filtering, and ordering on joined fields; grouping must target base fields only
Nested field selection with include_fields
Use a nested Ruby shape to select fields from joined entities. This compiles to Typesense include_fields with $assoc(field,…) segments.
- Reduces payload size and hydration work
- Keeps templates/view models explicit about data they require
- Unknown fields trigger guardrails with suggestions
- Multiple
include_fieldscalls merge and dedupe; first mention preserves order - Excludes win when present; effective selection is
include − excludeper path - Guardrails: unknown association →
InvalidJoin; unknown nested field →UnknownJoinFieldwith “did you mean …” hints
Filtering & ordering on joined fields
- Filters:
where(authors: { last_name: "Rowling" })→$authors.last_name:=“Rowling” - Sorts:
order(authors: { last_name: :asc })→$authors.last_name:asc - Quoting and reserved characters are handled by the sanitizer; booleans become
true/false,nilbecomesnull - Schema expectations: joined fields must exist in the target collection; base fields continue to work unchanged
Grouping semantics & groups API
group_by(:field, limit:, missing_values:) groups results and caps hits per group. Group order and within‑group order are preserved as returned by the backend. Sorting is applied before grouping, so it influences which hits become representatives within each group.
- Syntax:
.group_by(:author_id, limit: 1) - Difference between grouping order and result order:
- Group order is preserved in
Result#groups Result#hits/to_areturn the first hit per group (representatives) in group order- Changing
order(…)changes which hit is the representative, not the group order
- Group order is preserved in
- Access API:
Caveats:
- Grouping supports base fields only; joined paths like
$authors.last_nameare rejected - Large
group_limitcan increase memory use during hydration
Guardrails & errors
Common issues and actionable hints (messages include doc anchors; errors respond to#to_h for structured logging):
- Unknown association (e.g.,
joins(:authrs)): raisesInvalidJoin- Hint: “Declare it via
join :authors, …or check spelling” - See: Joins
- Hint: “Declare it via
- Join not applied (filter/order/select on joined fields without
.joins(:authors)): raisesJoinNotApplied- Hint: “Call
.joins(:authors)before filtering/sorting/selecting nested fields” - See: Joins
- Hint: “Call
- Unknown joined field (e.g.,
authors: [:middle_name]): raisesUnknownJoinField- Hint: “Did you mean
:first_name?” (suggestions provided) - See: Joins
- Hint: “Did you mean
- Invalid grouping limit (e.g.,
limit: 0): raisesInvalidGroup- Hint: “Provide a positive integer; omit to use server default”
- See: Grouping
- Non‑boolean
missing_values(e.g.,“yes”): raisesInvalidGroup- Hint: “Use
true/false;nilomits the parameter” - See: Grouping
- Hint: “Use
- Grouping on joined path (e.g.,
$authors.last_name): raisesUnsupportedGroupField- Hint: “Group by a base field (e.g.,
:author_id)” - See: Grouping
- Hint: “Group by a base field (e.g.,
- “Conflicting” order with grouping (surprising results): compiles, but remember sort is applied before grouping; it selects the representatives, not the group order
- Hint: “Adjust sort to pick desired representatives; group order is preserved”
- See: Grouping
Debugging & DX
Inspect without network I/O; all outputs are redacted and copy‑pastable.Multi‑diagram story
Association map (Book ↔ Author)
State → Params mapping (running example)
Grouped response shaping (representatives per author)
Performance notes
- Prefer minimal nested
include_fieldsto avoid payload bloat and faster hydration - Grouping affects pagination:
per_pageapplies to number of groups;group_limitcaps hits per group - Sorting before grouping determines representatives; be explicit about sort fields and directions
- Use compact logging with sampling to diagnose: