Related: Relation, Query DSL, DX, DebuggingDocumentation Index
Fetch the complete documentation index at: https://nikita-shkoda.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Intro
UseRelation to compose safe, immutable searches and the Query DSL to express predicates.
Prefer it over raw client calls for:
- Safety: quoting, validation, and redaction are centralized
- Immutability: AR‑style chainers return new relations without mutation
- Debuggability:
explain,to_curl,dry_run!visualize requests with zero I/O
Building a query
where accepts hashes, raw strings, or template fragments with placeholders. Multiple
where calls compose with AND semantics.
Basics:
- Primitives:
where(active: true)→active:=true - Arrays (IN):
where(brand_id: [1, 2, 3])→brand_id:=[1, 2, 3] - Template + args:
where(["price >= ?", 100]),where(["price <= ?", 200]) - Raw escape hatch:
where("price:>100 && price:<200")
- 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 Query DSL)
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):- where(…): adds predicates (see edge‑case note on Ruby
Rangebelow) - where.not(…): negates predicates; special handling for array‐empty with
empty_filtering:(see below) - order(updated_at: :desc): compiles to
sort_by: “updated_at:desc” - page(2).per(20): compiles to
page: 2, per_page: 20
Range (e.g., 100..200) is not a first‑class numeric range literal in
filter_by. Prefer two comparators:
NOT IN […] in explain output.
Grouping
Group by a single field and optionally control per‑group hit count and whether missing values form their own group:- Limits:
group_limitmust be a positive integer when present - Missing values: included only when
true - Ordering: Group order and within‑group hit order are preserved
- Selection: Selection applies to hydrated hits; nested includes are unaffected
- Sorting: Sort is applied before grouping; within‑group order follows backend order
Advanced chaining & options
Fine-tune the request with low-level controls:- search(q): set the query string (default
”*”). - limit(n) / offset(n): set strict limit/offset (alternative to
page/per). - cache(bool): toggle
use_cachefor this request. - use_synonyms(bool): toggle
use_synonyms. - use_stopwords(bool): toggle
use_stopwords(maps toremove_stop_words). - options(hash): shallow-merge arbitrary options into the request (e.g.
options(prioritize_exact_match: true)). - unscope(*parts): remove state pieces (e.g.
unscope(:where, :order)).
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) |
use_stopwords(false)setsremove_stop_words=true;nilclears the override.limit/offsetmap toper_page/pageonly whenpage/perare not set.- Prefer dedicated chainers for joins/selection/grouping/presets;
optionsis shallow on the params hash.
Joins & presets (orientation)
- Joins: Declare associations on the model, apply with
.joins(:assoc), then filter and select using nested shapes (e.g.,where(authors: { last_name: "Rowling" }),include_fields(authors: [:first_name])). See Joins and Field Selection. - Presets: Attach server‑side bundles of defaults and choose a mode
(
:merge,:only,:lock). See Presets and DX.
Debugging
Zero‑I/O helpers:- All outputs are redacted and stable for copy‑paste
dry_run!validates and returns a redacted body; no HTTP requests are made- Use
explainto preview grouping, joins, presets/curation, conflicts, and events
Compiler mapping
Relation state compiles into Typesense params deterministically. High‑level mapping: See: Compiler for precedence, quoting rules, and join context.Edge cases
-
Quoting: strings are double‑quoted; booleans
true/false;nil→null; arrays are one‑level flattened and quoted element‑wise. Time inputs are normalized per field type: numeric:time/:datetimefields prefer epoch seconds;:time_string/:datetime_stringuse ISO8601 (see Compiler). - Booleans vs strings: boolean fields coerce “true”/“false”; other fields treat strings literally (see Joins).
-
Empty arrays: membership operators require non‑empty arrays. If you enable
empty_filtering: trueon an array attribute, the following rewrites apply:.where(promotion_ids: [])→promotion_ids_empty:=true.where.not(promotion_ids: [])→promotion_ids_empty:=false
empty_filteringenabled for that field (hidden$assoc.field_emptyexists). Otherwise an empty array remains invalid. - Range endpoints: express with two comparators (see Chaining note above)
-
nil/missing:
nilcompiles tonull; usenot_eq(field, nil)ornot_in(field, [nil])to exclude nulls - Unicode/locale: collation/tokenization follow index settings; normalize inputs in your app if needed
-
Joined fields: require
.joins(:assoc)before filtering/sorting/selection on$assoc.field(see Joins). -
Grouping field: base fields only; joined paths like
$assoc.fieldare rejected (see Grouping). - Special characters: raw fragments are passed through; prefer templates for quoting
Selection, grouping & faceting
See Faceting for first-class faceting DSL:facet_by, facet_query, compiler mapping and result helpers.
Related links: Relation, Query DSL, DX, Joins, Field Selection, Compiler, Grouping, Observability