Skip to main content
Related: Client, Compiler, Observability, DX Relation is an immutable, chainable query object bound to a model class. It accumulates normalized query state without mutating previous instances.

Quick start

class SearchEngine::Book < SearchEngine::Base; end

r_1 = SearchEngine::Book.all
r_2 = r_1.where(category: 'fiction').order(:title).select(:id, :title).page(2).per(10)
# r_1 is unchanged
r_1.object_id != r_2.object_id #=> true
r_1.empty?                    #=> true

Immutability

Every chainer creates a new instance via copy-on-write. The original relation remains unchanged.
r_1 = SearchEngine::Book.all
r_2 = r_1.where(price: 10)
r_1.object_id #=> 701...
r_2.object_id #=> 702...
r_1.empty?     #=> true
r_2.empty?     #=> false

API

  • all: returns the relation itself (parity with AR).
  • where(*args): add filters. Accepts Hash, String/Symbol, arrays thereof.
  • order(value): add order expressions. Accepts Hash or String.
  • select(*fields) / exclude(*fields) / reselect(*fields): field selection DSL. See Field Selection and JOINs.
  • limit(n), offset(n), page(n), per(n): numeric setters; coerced with validation (see below).
  • options(opts = ): shallow-merge additional options for future adapters.
  • empty?: true when state equals the default empty state.
  • inspect: AR-style preview that materializes up to 11 hydrated records and renders #<SearchEngine::Relation [ … ]>. The 11th slot is rendered as when there are more than 10 records. To restore the previous zero‑I/O concise summary, set SearchEngine.config.relation_print_materializes = false.
See Materializers for execution methods (to_a, each, first, last, take, pluck, ids, count, exists?).
Note: .find(id) exists only on the model class as a convenience alias for find_by(id: id). It is intentionally not available on relations.

Lifecycle

See also: Compiler. See Client for execution context.

Filtering with where

Use where with hash input, a template, or a raw fragment. Each call ANDs predicates.
SearchEngine::Book
  .where(published: true)
  .where(["price >= ?", 100])
  .where("category_id:=[1,2,3]")
  • Hash form validates fields when attributes are declared.
  • Template form uses ? placeholders and safe quoting.
  • Raw strings are passed through as an escape hatch.
  • Call .joins(:assoc) before filtering on joined fields.
  • Use .where.not(…) to negate predicates.
For predicate parsing rules and AST details, see Query DSL.

order / select / pagination

SearchEngine::Book
  .order(updated_at: :desc)
  .select(:id, :title)
  .page(2).per(20)
  • order(value): accepts a Hash like { field: :asc, other: :desc } or a String like “field:asc,other:desc”. Directions are case-insensitive and normalized to asc/desc. Duplicate fields are de-duplicated with last-wins semantics.
  • select(*fields): accepts symbols/strings or arrays; trims and de-duplicates preserving first occurrence. If the model declares attributes, unknown fields raise. See Field Selection.
  • limit(n) / offset(n): numeric. limit >= 1, offset >= 0.
  • page(n) / per(n): numeric. page >= 1, per >= 1. The per(n) method writes to per_page internally.

Compiled params and debugging

  • Relation#to_typesense_params: compile immutable state to a Typesense body params object.
  • Relation#to_h: return the compiled params as a Hash (deterministic order).
  • Relation#inspect: preview hydrated results; control with SearchEngine.config.relation_print_materializes.
For compilation rules and parameter mapping, see Compiler and Debugging.