Skip to main content
Related: Relation, Query DSL, DX

Intro

Copy these minimal patterns and adapt fields to your models. Prefer dry_run! and explain to debug locally without network I/O.

Index of patterns

Back to top ⤴

Recipes

Exact match

Small set lookup by ID.
SearchEngine::Book.where(id: 42)
Why it works / gotchas:

Prefix/infix match

Begins‑with or contains‑like filters via templates/raw.
SearchEngine::Book.where(["name PREFIX ?", "mil"])  # prefix
SearchEngine::Book.where("name:~=milk")               # infix (raw)
Why / gotchas:
  • PREFIX is parsed to AST; infix shown as raw Typesense fragment
  • Consider query text in q for full‑text; this is a filter
  • Links: Query DSL, Compiler

Any of list (IN)

Match any of several brands.
SearchEngine::Book.where(author_id: [1, 2, 3])
Why / gotchas:

Price range + sort

Use two comparators for numeric ranges.
SearchEngine::Book
  .where(["price >= ?", 100])
  .where(["price <= ?", 200])
  .order(price: :asc)
Why / gotchas:

Pagination

Classic page/per; prefer this over limit/offset unless you need offset math.
SearchEngine::Book.page(2).per(20)
Why / gotchas:

Facet filters

Combine multiple filters with AND semantics across calls.
SearchEngine::Book
  .where(category: "dairy")
  .where(author_id: [1, 2])
  .where(["price <= ?", 500])
Why / gotchas:

Pinned/hidden curation

Pin two IDs and hide one; keep network‑safe while inspecting.
rel = SearchEngine::Book.pin("p_12", "p_34").hide("p_99")
rel.dry_run!
Why / gotchas:
  • Curation keys are body‑only; redacted in logs
  • Hide wins when an ID is both pinned and hidden
  • Links: Curation, Observability

Grouping top‑N

First hit per group, up to N hits inside each.
SearchEngine::Book.group_by(:author_id, limit: 2)
Why / gotchas:
  • group_limit caps hits per group; pagination applies to number of groups
  • Links: Grouping

Joins — basic

Filter on a joined collection field.
SearchEngine::Book
  .joins(:authors)
  .where(authors: { last_name: "Rowling" })
  .include_fields(authors: [:first_name])
Why / gotchas:
  • Call .joins(:assoc) before referencing $assoc.field
  • Links: Joins, Compiler

Multi‑search two relations

Send two labeled relations in one round‑trip.
res = SearchEngine.multi_search do |m|
  m.add :books, SearchEngine::Book.where(category: "dairy").per(5)
  m.add :publishers,   SearchEngine::Publisher.where(["name PREFIX ?", "mil"]).per(3)
end
Why / gotchas:
  • Per‑search params compiled independently; order preserved by labels
  • Links: Multi‑search

Debug each recipe

Use explain or dry_run! to preview without I/O:
rel = SearchEngine::Book.where(active: true).order(updated_at: :desc).per(10)
puts rel.explain
rel.dry_run!
Redaction policy hides literals and secrets; bodies remain copyable. Back to top ⤴

Edge‑case callouts

  • Quoting: strings double‑quoted; booleans and null literal; arrays flattened one level
  • Boolean coercion: only for boolean‑typed fields; strings “true”/“false” accepted
  • Empty arrays: invalid for membership; provide at least one value
  • Reserved characters: prefer templates with placeholders to avoid manual escaping
  • Ambiguous names: unknown fields raise with suggestions when attributes are declared
  • Sort vs group order: sort applies before grouping; group order preserved

Related links: Query DSL, Compiler, DX, Observability, Joins, Grouping, Presets, Curation

Faceting DSL

Add facets and facet queries:
rel = SearchEngine::Book
  .facet_by(:author_id, max_values: 20)
  .facet_query(:price, "[0..9]", label: "under_10")

res = rel.execute
res.facet_values("author_id") # => array of { value:, count:, ... }
See Faceting for details.