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

# Relation Guide

> Compose safe, immutable searches with the Relation DSL and Query DSL. Chaining, grouping, joins/presets orientation, debugging, and compiler mapping.

Related: <a href="/projects/search-engine-for-typesense/v29/relation">Relation</a>, <a href="/projects/search-engine-for-typesense/v29/query-dsl">Query DSL</a>, <a href="/projects/search-engine-for-typesense/v29/dx">DX</a>, <a href="/projects/search-engine-for-typesense/v29/debugging">Debugging</a>

## Intro

Use <code>Relation</code> to compose safe, immutable searches and the Query DSL to express predicates.
Prefer it over raw client calls for:

* <strong>Safety</strong>: quoting, validation, and redaction are centralized
* <strong>Immutability</strong>: AR‑style chainers return new relations without mutation
* <strong>Debuggability</strong>: <code>explain</code>, <code>to\_curl</code>, <code>dry\_run!</code> visualize requests with zero I/O

See also: <a href="/projects/search-engine-for-typesense/v29/relation">Relation</a> and <a href="/projects/search-engine-for-typesense/v29/query-dsl">Query DSL</a>.

## Building a query

<code>where</code> accepts hashes, raw strings, or template fragments with placeholders. Multiple
<code>where</code> calls compose with AND semantics.

Basics:

* <strong>Primitives</strong>: <code>where(active: true)</code> → <code>active:=true</code>
* <strong>Arrays (IN)</strong>: <code>where(brand\_id: \[1, 2, 3])</code> → <code>brand\_id:=\[1, 2, 3]</code>
* <strong>Template + args</strong>: `where(["price >= ?", 100])`, `where(["price <= ?", 200])`
* <strong>Raw escape hatch</strong>: `where("price:>100 && price:<200")`

Notes:

* Field names are validated against model attributes when declared
* Placeholders are strictly arity‑checked and safely quoted by the sanitizer
* OR semantics require a raw fragment (e.g., "a:=1 || b:=2") or higher‑level AST usage (see <a href="/projects/search-engine-for-typesense/v29/query-dsl">Query DSL</a>)

## Chaining

Use AR‑style chainers to add sorting, selection, and pagination. Chain order does not
matter; the compiler emits a deterministic param set.

Verbatim example (chaining):

```ruby theme={null}
SearchEngine::Book.where(price: 100..200).order(updated_at: :desc).page(2).per(20)
```

What it shows:

* <strong>where(...)</strong>: adds predicates (see edge‑case note on Ruby <code>Range</code> below)
* <strong>where.not(...)</strong>: negates predicates; special handling for array‐empty with <code>empty\_filtering:</code> (see below)
* <strong>order(updated\_at: :desc)</strong>: compiles to <code>sort\_by: "updated\_at:desc"</code>
* <strong>page(2).per(20)</strong>: compiles to <code>page: 2, per\_page: 20</code>

Range note: Ruby <code>Range</code> (e.g., <code>100..200</code>) is not a first‑class numeric range literal in
<code>filter\_by</code>. Prefer two comparators:

```ruby theme={null}
SearchEngine::Book
  .where(["price >= ?", 100])
  .where(["price <= ?", 200])
```

See: <a href="/projects/search-engine-for-typesense/v29/dx">DX</a> to preview compiled params. NOT-IN is rendered as <code>NOT IN \[...]</code> in explain output.

## Grouping

Group by a single field and optionally control per‑group hit count and whether missing
values form their own group:

```ruby theme={null}
rel = SearchEngine::Book.group_by(:author_id, limit: 1, missing_values: true)
rel.to_typesense_params
# => { q: "*", query_by: "title, description", group_by: "author_id", group_limit: 1,
#      group_missing_values: true }
```

Caveats and interactions:

* <strong>Limits</strong>: <code>group\_limit</code> must be a positive integer when present
* <strong>Missing values</strong>: included only when <code>true</code>
* <strong>Ordering</strong>: Group order and within‑group hit order are preserved
* <strong>Selection</strong>: Selection applies to hydrated hits; nested includes are unaffected
* <strong>Sorting</strong>: Sort is applied before grouping; within‑group order follows backend order

See: <a href="/projects/search-engine-for-typesense/v29/grouping">Grouping</a>, <a href="/projects/search-engine-for-typesense/v29/relation-reference">Relation Guide</a>, <a href="/projects/search-engine-for-typesense/v29/field-selection">Field Selection</a>.

[Back to top ⤴](#relation-guide)

## Advanced chaining & options

Fine-tune the request with low-level controls:

* <strong>search(q)</strong>: set the query string (default <code>"\*"</code>).
* <strong>limit(n) / offset(n)</strong>: set strict limit/offset (alternative to <code>page</code>/<code>per</code>).
* <strong>cache(bool)</strong>: toggle <code>use\_cache</code> for this request.
* <strong>use\_synonyms(bool)</strong>: toggle <code>use\_synonyms</code>.
* <strong>use\_stopwords(bool)</strong>: toggle <code>use\_stopwords</code> (maps to <code>remove\_stop\_words</code>).
* <strong>options(hash)</strong>: shallow-merge arbitrary options into the request (e.g. <code>options(prioritize\_exact\_match: true)</code>).
* <strong>unscope(\*parts)</strong>: remove state pieces (e.g. <code>unscope(:where, :order)</code>).

```ruby theme={null}
SearchEngine::Book
  .search("potter")
  .limit(5)
  .cache(false)
  .options(prioritize_exact_match: false)
```

### Additional chainers → params

| Chainer                  | Effect                                                                                            | Typesense param     |
| ------------------------ | ------------------------------------------------------------------------------------------------- | ------------------- |
| `search(q)`              | Set query string (default `"*"`)                                                                  | `q`                 |
| `options(opts)`          | Shallow-merge low-level options (e.g., `query_by`, `infix`, `selection.strict_missing`)           | Various body keys   |
| `limit(n)` / `offset(n)` | Limit/offset alternative when page/per are absent                                                 | `per_page`, `page`  |
| `cache(true/false/nil)`  | Toggle URL-level caching per call                                                                 | `use_cache` (URL)   |
| `use_synonyms(val)`      | Enable/disable synonyms                                                                           | `enable_synonyms`   |
| `use_stopwords(val)`     | Enable/disable stopwords (inverted)                                                               | `remove_stop_words` |
| `unscope(*parts)`        | Remove relation state (e.g., `:where`, `:order`, `:select`, `:limit`, `:offset`, `:page`, `:per`) | N/A (state cleared) |

Notes:

* `use_stopwords(false)` sets `remove_stop_words=true`; `nil` clears the override.
* `limit/offset` map to `per_page`/`page` only when `page/per` are not set.
* Prefer dedicated chainers for joins/selection/grouping/presets; `options` is shallow on the params hash.

## Joins & presets (orientation)

* <strong>Joins</strong>: Declare associations on the model, apply with <code>.joins(:assoc)</code>, then filter
  and select using nested shapes (e.g., `where(authors: { last_name: "Rowling" })`,
  <code>include\_fields(authors: \[:first\_name])</code>). See
  <a href="/projects/search-engine-for-typesense/v29/joins">Joins</a> and
  <a href="/projects/search-engine-for-typesense/v29/field-selection">Field Selection</a>.
* <strong>Presets</strong>: Attach server‑side bundles of defaults and choose a mode
  (<code>:merge</code>, <code>:only</code>, <code>:lock</code>). See
  <a href="/projects/search-engine-for-typesense/v29/presets">Presets</a> and
  <a href="/projects/search-engine-for-typesense/v29/dx">DX</a>.

Keep deeper usage to those pages; this guide focuses on composition basics.

## Debugging

Zero‑I/O helpers:

```ruby theme={null}
rel = SearchEngine::Book.where(published: true).order(updated_at: :desc).page(2).per(20)
rel.to_params_json           # redacted request body as JSON
rel.to_curl                  # single‑line, redacted curl
rel.dry_run!                 # { url:, body:, url_opts: } — no network I/O
puts rel.explain             # concise human summary
```

* All outputs are redacted and stable for copy‑paste
* <code>dry\_run!</code> validates and returns a redacted body; no HTTP requests are made
* Use <code>explain</code> to preview grouping, joins, presets/curation, conflicts, and events

See: <a href="/projects/search-engine-for-typesense/v29/dx">DX</a>, <a href="/projects/search-engine-for-typesense/v29/observability">Observability</a>.

[Back to top ⤴](#relation-guide)

## Compiler mapping

Relation state compiles into Typesense params deterministically. High‑level mapping:

```mermaid theme={null}
flowchart LR
  subgraph R[Relation state]
    A[AST (where)]
    B[orders]
    C[selection<br/>include/exclude]
    D[grouping]
    E[preset name+mode]
    F[options (q,infix)]
  end
  R --> G[Compiler]
  G --> H[Params]
  H -->|keys| I[q, query_by, filter_by, sort_by,
per_page/page, include/exclude, group_* , preset]
  I --> J[Client]
```

See: <a href="/projects/search-engine-for-typesense/v29/compiler">Compiler</a> for precedence, quoting rules, and join context.

## Edge cases

* <strong>Quoting</strong>: strings are double‑quoted; booleans <code>true/false</code>; <code>nil</code> → <code>null</code>; arrays are one‑level
  flattened and quoted element‑wise. Time inputs are normalized per field type:
  numeric <code>:time/:datetime</code> fields prefer epoch seconds; <code>:time\_string/:datetime\_string</code> use ISO8601 (see <a href="/projects/search-engine-for-typesense/v29/compiler">Compiler</a>).
* <strong>Booleans vs strings</strong>: boolean fields coerce "true"/"false"; other fields treat strings literally (see <a href="/projects/search-engine-for-typesense/v29/joins">Joins</a>).
* <strong>Empty arrays</strong>: membership operators require non‑empty arrays. If you enable <code>empty\_filtering: true</code> on an array attribute, the following rewrites apply:

  * <code>.where(promotion\_ids: \[])</code> → <code>promotion\_ids\_empty:=true</code>
  * <code>.where.not(promotion\_ids: \[])</code> → <code>promotion\_ids\_empty:=false</code>

  For joined fields, rewrite is applied only when the joined collection has <code>empty\_filtering</code> enabled for that field (hidden <code>\$assoc.field\_empty</code> exists). Otherwise an empty array remains invalid.
* <strong>Range endpoints</strong>: express with two comparators (see Chaining note above)
* <strong>nil/missing</strong>: <code>nil</code> compiles to <code>null</code>; use <code>not\_eq(field, nil)</code> or <code>not\_in(field, \[nil])</code> to exclude nulls
* <strong>Unicode/locale</strong>: collation/tokenization follow index settings; normalize inputs in your app if needed
* <strong>Joined fields</strong>: require <code>.joins(:assoc)</code> before filtering/sorting/selection on <code>\$assoc.field</code> (see <a href="/projects/search-engine-for-typesense/v29/joins">Joins</a>).
* <strong>Grouping field</strong>: base fields only; joined paths like <code>\$assoc.field</code> are rejected (see <a href="/projects/search-engine-for-typesense/v29/grouping">Grouping</a>).
* <strong>Special characters</strong>: raw fragments are passed through; prefer templates for quoting

[Back to top ⤴](#relation-guide)

### Selection, grouping & faceting

See <a href="/projects/search-engine-for-typesense/v29/faceting">Faceting</a> for first-class faceting DSL: <code>facet\_by</code>, <code>facet\_query</code>, compiler mapping and result helpers.

***

Related links: <a href="/projects/search-engine-for-typesense/v29/relation">Relation</a>, <a href="/projects/search-engine-for-typesense/v29/query-dsl">Query DSL</a>, <a href="/projects/search-engine-for-typesense/v29/dx">DX</a>,
<a href="/projects/search-engine-for-typesense/v29/joins">Joins</a>, <a href="/projects/search-engine-for-typesense/v29/field-selection">Field Selection</a>, <a href="/projects/search-engine-for-typesense/v29/compiler">Compiler</a>,
<a href="/projects/search-engine-for-typesense/v29/grouping">Grouping</a>, <a href="/projects/search-engine-for-typesense/v29/observability">Observability</a>
