Global Search
The cross-resource spotlight search ships in the paid habarico-enterprise gem — install it and
require "habarico_enterprise", see Installation. Without it
the global_fulltext_search_enabled option below does not exist, while per-resource search
(search_fields) keeps working in the free gem.
Enable a resource to appear in the global fulltext search results:
global_fulltext_search_enabled true
When enabled, the SearchService runs Habarico's typed fulltext search (see search_fields below) against this resource and returns matching records.
The frontend calls GET {serviceUrl}/search/?search=…, optionally with &limit=…. Each result
exposes id, title (sourced from record.representation when available) and highlights (see
below). A blank search returns an empty array without touching the database.
The query parameter is search, not query. Both backends read the same name, which is what lets
one frontend build talk to either.
Result limit
limit applies per resource: every resource with global_fulltext_search_enabled contributes
up to limit results to the same response.
It must be an integer between 1 and the configured ceiling. Anything else — a non-numeric value,
zero, a negative number, or a number above the ceiling — is refused with 422 rather than
silently corrected, the same way an over-wide aggregation is refused. Omitting limit (or sending
limit= with nothing after it) falls back to the default. Both ends are configurable:
# config/initializers/habarico.rb
HabaricoEnterprise::Search::Config.default_limit = 5
HabaricoEnterprise::Search::Config.max_limit = 100 # nil removes the ceiling entirely
The ceiling sits far below the page size of a resource's own list endpoint on purpose: the spotlight
materializes up to limit records of every enabled resource and runs the match highlighter over
each one. To read many rows of a single resource, page its list endpoint instead.
The rule lives in HabaricoEnterprise::Search::Limit.resolve, which the controller applies before
the search fans out. SearchService#get_search_result takes the resolved integer, so a host calling
it directly is responsible for the value it passes — run it through Limit.resolve to get the same
validation the endpoint performs.
search_fields
Declare which fields the typed fulltext search matches against. Paths may traverse relations via
__ (e.g. author__name):
search_fields %i[title description author__name status published]
search_fields is the single source of truth for typed matching and match highlighting. Each path
is resolved against the model via ActiveRecord reflection (associations) — or Habarico references —
with the leaf field classified into one of four search types:
| Type | Detected from | Matching |
|---|---|---|
:text | string / text columns | case-insensitive substring (lower(…) LIKE) |
:choices | enum attributes (has_enum_attr?) | against the human-readable choice labels |
:boolean | boolean columns | against the translated Yes / No labels |
:numeric | integer / float / decimal columns | cast-to-text substring (e.g. 12 matches 121.00) |
Recommended field types are text, choices, boolean and numeric — they produce useful matches and
aligned highlights. Not recommended: date / datetime and uuid columns are classified as
:text and technically match their ISO/raw form, but the FE usually renders them differently, so
highlight offsets won't line up with what the user sees — use a range filter for dates instead.
Unresolvable paths (a missing field, or a non-relation intermediate hop) are skipped, so a stale
entry never breaks the request. search_fields is optional, but without it the matcher never
narrows anything: an enabled resource with no search_fields returns all records — up to the
limit — for any search query (and the list endpoint returns all records unfiltered).
Habarico owns the whole search query (it does not delegate the search key to eluvia-base
filter_2!), so search_fields is authoritative and supports relation traversal and the unaccent
toggle. Both the service search endpoint and the resource list endpoint (?search=… or
?filter=search="…") apply the same matcher.
Diacritics-insensitive matching
Enable accent-insensitive matching (and highlighting) globally:
# config/initializers/habarico.rb
Habarico.setup do |config|
config.search_unaccent_enabled = true
end
When on, text matching wraps columns in the PostgreSQL unaccent() function (the extension must be
installed) and the highlighter strips accents when locating matches, mapping the offsets back onto
the original, accented value. Off by default.
Highlight sidecar
When a search is active, every matched record carries a highlights payload — on the service search
endpoint as objects[i].highlights (trimmed to a snippet around the matches) and on the resource
list endpoint as each row's meta.highlights (the full field value). Both share the same shape:
[
{
"name": "author__name",
"value": "<mark>Alpha</mark> article",
"label": "Author name",
"offsets": [[0, 5]]
}
]
name— thesearch_fieldspath that matched.value— HTML-escaped field value with each matched substring wrapped in<mark>...</mark>. Choice fields and booleans use their human-readable display (the enum label, the translatedYes/No).label— the field's human-readable label.offsets— half-open[start, end]spans of each match in the original (raw, non-escaped) value, independent of any snippet trimming applied tovalue.