> ## Documentation Index
> Fetch the complete documentation index at: https://nikita-shkoda.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Field Selection

> Concise, immutable DSL on Relation for selecting/excluding fields, including nested join fields. Normalization, precedence, and compiler mapping.

Related: <a href="/projects/search-engine-for-typesense/v30/relation">Relation</a>, <a href="/projects/search-engine-for-typesense/v30/joins">Joins</a>, <a href="/projects/search-engine-for-typesense/v30/materializers">Materializers</a>

A concise, immutable DSL on <code>Relation</code> for selecting or excluding fields, with support for nested join fields and normalization.

## Overview

* <code>select(\*fields)</code>: add fields to include list (root and nested); immutable, deduped, order preserved by first mention.
* <code>exclude(\*fields)</code>: add fields to exclude list (root and nested); immutable, deduped, order preserved by first mention.
* <code>reselect(\*fields)</code>: clear prior include/exclude state and set a new include list.

Nested fields are addressed via association-name keyed Hashes and require the association to be joined first.

```ruby theme={null}
# Internal example state
{ include: Set[:id, :name], include_nested: { authors: Set[:first_name, :last_name] },
  exclude: Set[:legacy], exclude_nested: { brands: Set[:internal_score] } }
```

## DSL

* Root fields: symbols/strings (e.g., <code>:id, "title"</code>)
* Nested fields: `{ assoc => [:field, ...] }`
* <code>reselect</code> replaces both include and exclude state

See also: <a href="/projects/search-engine-for-typesense/v30/relation-reference">Relation Guide</a> and <a href="/projects/search-engine-for-typesense/v30/joins">Joins</a> for DSL context.

## Usage

```ruby theme={null}
SearchEngine::Book
  .select(:id, :name)
  .exclude(:internal_score)
```

```ruby theme={null}
SearchEngine::Book
  .joins(:authors)
  .select(:id, :title, authors: [:first_name, :last_name])
  .exclude(authors: [:middle_name])
```

## 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 <code>include − exclude</code> (applied for root and each nested association).
  * <code>reselect</code> clears both include and exclude state.

### Nested joins

* Nested shapes require <code>joins(:assoc)</code> beforehand.
* For nested paths without explicit includes, the engine attempts to derive “all fields” from the joined collection’s declared <code>attributes</code> and subtract explicit excludes. If unknown, nested excludes may be emitted via <code>exclude\_fields</code>.

## Inspect / Explain

* <code>inspect</code> includes compact tokens for current selection state, e.g. <code>sel="..." xsel="..."</code>.
* <code>explain</code> prints human-readable <code>select:</code> and <code>exclude:</code> lines when present and a compact one-line summary of the effective selection after precedence.

## State → Params mapping

```mermaid theme={null}
flowchart TD
  A[DSL input: select/exclude/reselect] --> B[Normalization: include/exclude (root & nested)]
  B --> C[Precedence: effective = include − exclude per path]
  C --> D[Compiler encoders]
  D --> E[Typesense params: include_fields / exclude_fields]
```

## Compiler Mapping (Typesense params)

* <strong>include\_fields</strong>: base tokens and nested joins encoded as <code>\$assoc(field1,field2)</code>.
* <strong>exclude\_fields</strong>: base tokens and nested joins encoded similarly.
* <strong>Precedence</strong>: final effective set is <code>include − exclude</code> per path (root and each association). <strong>Exclude wins</strong>. Empty groups are omitted.

### Mapping table

| Normalized state                                                                                                                                             | include\_fields                           | exclude\_fields                  |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------- | -------------------------------- |
| include: `[:id, :name]`                                                                                                                                      | `id,name`                                 | —                                |
| exclude: `[:legacy]`                                                                                                                                         | —                                         | `legacy`                         |
| include: `[:id]`, include\_nested: `{ authors: ["first_name","last_name"] }`                                                                                 | `$authors(first_name,last_name),id`       | —                                |
| exclude\_nested: `{ brands: ["internal_score"] }`                                                                                                            | —                                         | `$brands(internal_score)`        |
| include: `[:id,:title]`, include\_nested: `{ authors: ["first_name","last_name"] }`, exclude: `[:legacy]`, exclude\_nested: `{ brands: ["internal_score"] }` | `id,title,$authors(first_name,last_name)` | `legacy,$brands(internal_score)` |
| include\_nested: `{ authors: ["first_name","last_name"] }`, exclude\_nested: `{ authors: ["last_name"] }`                                                    | `$authors(first_name)`                    | —                                |

### Flow

```mermaid theme={null}
flowchart TD
  A[Normalized selection state] --> B[Apply precedence: include − exclude per path]
  B --> C[Encode root: sort + join]
  B --> D[Encode nested: $assoc(field1,field2) sorted]
  C --> E[include_fields]
  D --> E
  F[Raw excludes (root + nested)] --> G[Sort + encode ($assoc(...))]
  G --> H[exclude_fields]
```

### Example

```ruby theme={null}
rel = SearchEngine::Book
        .joins(:authors, :publishers)
        .select(:id, :title, authors: [:first_name, :last_name])
        .exclude(:legacy, publishers: [:internal_score])
rel.to_typesense_params[:include_fields]
# => "id,title,$authors(first_name,last_name)"
rel.to_typesense_params[:exclude_fields]
# => "legacy,$publishers(internal_score)"
```

## Strict vs Lenient selection

Hydration respects selection by assigning only attributes present in each hit. Missing attributes are never synthesized.

* <strong>Lenient (default)</strong>: Missing requested fields are left unset; readers should return <code>nil</code> if they rely on ivars.
* <strong>Strict</strong>: If a requested field is absent in the hit, hydration raises <code>SearchEngine::Errors::MissingField</code> with guidance.

Backed by:

* Per‑relation override via `options(selection: { strict_missing: true })`
* Global default via `SearchEngine.configure { |c| c.selection = OpenStruct.new(strict_missing: false) }`

```ruby theme={null}
# initializer
SearchEngine.configure { |c| c.selection = OpenStruct.new(strict_missing: false) }
# per relation
rel = SearchEngine::Book.select(:id).options(selection: { strict_missing: true })
```

### Propagation and enforcement

* During compile, the effective base selection is captured as <code>requested\_root</code> and the strict flag is recorded.
* During hydration, <code>Result</code> computes <code>missing = requested\_root − present\_keys</code> for each hit; when strict, it raises <code>MissingField</code> with a helpful message and a pointer back to these docs.
* When includes are empty (effective “all fields”), no <code>requested\_root</code> is set and strict enforcement is a no‑op.

See also: <a href="/projects/search-engine-for-typesense/v30/materializers">Materializers</a> for validation on <code>pluck</code>, and <a href="/projects/search-engine-for-typesense/v30/materializers">Materializers</a> 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.

* <strong>UnknownField</strong>: base attribute not declared on the model.
* <strong>UnknownJoinField</strong>: nested attribute not declared on the given association.
* <strong>ConflictingSelection</strong>: invalid/ambiguous shapes that cannot be normalized deterministically.

Example:

```text theme={null}
UnknownJoinField: :middle_name is not declared on association :authors for SearchEngine::Book
```

Notes:

* 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.

## Hydration decision (strict vs lenient)

```
```
