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