- What it is: federates multiple
Relations into one request; results are mapped back to labels in order. - When to use: homepage modules, typeahead+categories, federated search blocks; prefer it over multiple sequential calls to reduce latency.
- When not to use: completely independent pages with different lifecycles or when strong isolation/errors per request are critical.
Builder DSL
UseSearchEngine.multi_search(common: …) { |m| m.add(label, relation) }. The common: hash is shallow‑merged into each compiled relation payload; per‑search values from the relation win on key conflicts.
Required verbatim example:
- Override pagination per search while inheriting
common:: - Add field selection per search:
examples/demo_shop/app/controllers/search_controller.rb.
Per‑search overrides
Per‑relation chainers andoptions(…) override or augment common: on a per‑entry basis:
where/filters →filter_byorder→sort_byselect/exclude→include_fields/exclude_fieldspage/per→page/per_pageoptions(q: …, query_by: …, infix: …)map into the compiled body
query_by and filters override common:):
- Consistent
query_byper collection: ensure each collection’s fields exist; unknown fields raise during compile when strict field checks are enabled. - URL‑only knobs:
use_cache,cache_ttllive at the URL level and are filtered from bothcommon:and per‑search bodies.
Result handling with MultiResult
SearchEngine.multi_search returns SearchEngine::Multi::ResultSet (hash‑like). If you prefer a dedicated wrapper, use SearchEngine.multi_search_result which returns SearchEngine::MultiResult.
- Hash‑like access:
res[:books](matches the labels you added) - Each entry is a
SearchEngine::Resultwith#to_a,#found,#empty?,#raw, etc. - Order is preserved; labels are case‑insensitive symbols internally.
res[:books].to_a returns hydrated hits for the :books entry.
Example with MultiResult directly:
entry.raw to branch UI gracefully (avoid raising globally):
Controller usage patterns (Rails)
Keep controllers thin; build relations with only request‑dependent inputs and pass a single multi‑result to the view.- Prefer URL/request‑level cache keys derived from stable inputs (e.g.,
params.slice(:q, :page, :per, :filters)). - Multi‑search uses URL‑level cache knobs from config:
{ use_cache: SearchEngine.config.use_cache, cache_ttl: SearchEngine.config.cache_ttl_s }. - Per‑relation
options(use_cache:, cache_ttl:)are not applied in the multi‑search path; set them via config for multi‑search. - Consider setting HTTP cache headers at the controller/edge layer based on those inputs; avoid embedding secrets.
Compile flow
DX & debugging
Prefer network‑safe introspection for demos and debugging:- See the DX page for details and redaction policy.
- For fully offline tests/examples, use the stub client approach in the Testing page.
Presets & Curation in multi‑search
Applied per relation. Eachm.add carries its own preset/curation context and compiles independently.
- Presets: per‑search
presetandpreset_mode(merge/only/lock) are honored during compile. - Curation:
pinned_hits,hidden_hits,override_tags,filter_curated_hitsare emitted body‑only when present.
Edge cases & troubleshooting
- Misaligned
query_by: ensure each collection’s fields exist; validate withrel.explainandrel.dry_run!. - Unknown fields: selection and filtering validate against declared attributes; fix names or disable strictness per environment.
- Mixed grouping: grouping options are compiled per relation; UI should handle grouped vs non‑grouped results independently.
- Differing
per/page: expected; each box paginates independently. - Partial failures: the helper augments raised API errors with failing label when the HTTP status is non‑2xx; for 2xx with per‑entry errors, inspect
result.rawper label and degrade gracefully. - Redaction: never print API keys or raw
filter_bydirectly; usedry_run!,to_curl, orexplainwhich apply redaction.