Relation for selecting or excluding fields, with support for nested join fields and normalization.
Overview
select(*fields): add fields to include list (root and nested); immutable, deduped, order preserved by first mention.exclude(*fields): add fields to exclude list (root and nested); immutable, deduped, order preserved by first mention.reselect(*fields): clear prior include/exclude state and set a new include list.
DSL
- Root fields: symbols/strings (e.g.,
:id, “title”) - Nested fields:
{ assoc => [:field, ...] } reselectreplaces both include and exclude state
Usage
Normalization & Precedence
- Inputs accept symbols/strings and arrays; nested via
{ assoc => [:field, ...] }. - All names are coerced to symbols/strings consistently; blanks rejected; duplicates removed with first-mention preserved.
- Precedence:
- When include is empty, effective selection is “all fields” (when attributes are known) minus explicit excludes.
- When include is non-empty, effective selection is
include − exclude(applied for root and each nested association). reselectclears both include and exclude state.
Nested joins
- Nested shapes require
joins(:assoc)beforehand. - For nested paths without explicit includes, the engine attempts to derive “all fields” from the joined collection’s declared
attributesand subtract explicit excludes. If unknown, nested excludes may be emitted viaexclude_fields.
Inspect / Explain
inspectincludes compact tokens for current selection state, e.g.sel=”…” xsel=”…”.explainprints human-readableselect:andexclude:lines when present and a compact one-line summary of the effective selection after precedence.
State → Params mapping
Compiler Mapping (Typesense params)
- include_fields: base tokens and nested joins encoded as
$assoc(field1,field2). - exclude_fields: base tokens and nested joins encoded similarly.
- Precedence: final effective set is
include − excludeper path (root and each association). Exclude wins. Empty groups are omitted.
Mapping table
Flow
Example
Strict vs Lenient selection
Hydration respects selection by assigning only attributes present in each hit. Missing attributes are never synthesized.- Lenient (default): Missing requested fields are left unset; readers should return
nilif they rely on ivars. - Strict: If a requested field is absent in the hit, hydration raises
SearchEngine::Errors::MissingFieldwith guidance.
- Per‑relation override via
options(selection: { strict_missing: true }) - Global default via
SearchEngine.configure { |c| c.selection = OpenStruct.new(strict_missing: false) }
Propagation and enforcement
- During compile, the effective base selection is captured as
requested_rootand the strict flag is recorded. - During hydration,
Resultcomputesmissing = requested_root − present_keysfor each hit; when strict, it raisesMissingFieldwith a helpful message and a pointer back to these docs. - When includes are empty (effective “all fields”), no
requested_rootis set and strict enforcement is a no‑op.
pluck, and Materializers for hydration flow.
Guardrails & errors
Validation happens during chaining (after normalization, before mutating state) and raises actionable errors early. Suggestions are provided when attribute registries are available.- UnknownField: base attribute not declared on the model.
- UnknownJoinField: nested attribute not declared on the given association.
- ConflictingSelection: invalid/ambiguous shapes that cannot be normalized deterministically.
- Suggestion source: Levenshtein/prefix against the relevant registry (top 1–3, stable order).
- Overlap between include and exclude is allowed; precedence still applies. Conflicts are only about malformed shapes.