“Advanced” searches Rails’s nested attributes functionality in order to generate complex queries with nested AND/OR groupings, etc. This takes a bit more work but can generate some pretty cool search interfaces that put a lot of power in the hands of your users.
A notable drawback with these searches is that the increased size of the parameter string will typically force you to use the HTTP POST method instead of GET.
The search parameter structure
An advanced search is expressed as nested groupings. Each key has a short and a long spelling, and the two are interchangeable:
| Short | Long | Meaning |
|---|---|---|
g |
groupings |
Nested groupings |
c |
conditions |
The conditions in a grouping |
m |
combinator |
How this grouping’s members are joined |
a |
attributes |
The attributes a condition applies to |
p |
predicate |
The predicate to apply (predicate_name is accepted too) |
v |
values |
The values to match against |
Person.ransack(
g: [
{ m: 'or', name_cont: 'Ernie', email_cont: 'ernie' }
]
)
Groupings nest, so you can express (A OR B) AND (C OR D):
Person.ransack(
m: 'and',
g: [
{ m: 'or', name_cont: 'Ernie', email_cont: 'ernie' },
{ m: 'or', salary_gteq: 50_000, parent_name_eq: 'Ruby' }
]
)
Combinators
m (or combinator) takes 'and' or 'or', and defaults to 'and'. Case and
Symbols are accepted, so 'or', 'OR', 'Or' and :or all mean the same:
Person.ransack(g: [{ m: 'OR', name_eq: 'Ernie', email_eq: 'ernie@example.com' }])
# ... WHERE ("people"."name" = 'Ernie' OR "people"."email" = 'ernie@example.com')
An unrecognised combinator is ignored and the grouping falls back to 'and':
Person.ransack(g: [{ m: 'nand', name_eq: 'Ernie', email_eq: 'ernie@example.com' }])
# ... WHERE ("people"."name" = 'Ernie' AND "people"."email" = 'ernie@example.com')
This is deliberate — a search built from user input should not blow up on a bad
parameter. If you would rather hear about it, use ransack!, or set
ignore_unknown_conditions to false, and an unrecognised combinator raises
just as an unknown predicate or attribute does — at any level of nesting:
Person.ransack!(name_eq: 'Ernie', combinator: 'nand')
# Ransack::InvalidSearchError: Invalid combinator nand
Person.ransack!(g: [{ m: 'nand', name_eq: 'Ernie', email_eq: 'ernie@example.com' }])
# Ransack::InvalidSearchError: Invalid combinator nand
Ransack::InvalidSearchError is a subclass of ArgumentError, so existing
rescue ArgumentError handlers still catch it. A blank combinator, as a form
submits for an untouched field, is never an error.
Before Ransack 5.0,
'OR'and:orwere not recognised and silently fell back to'and'— a search that looked correct returned the wrong rows. See #1465.Blank values
A condition whose value is blank is dropped before the query is built, so a search form submitted with empty fields returns every record rather than none. This applies to the low-level
c:form as well as the shorthand:Person.ransack(children_name_eq: '').result.to_sql # => SELECT "people".* FROM "people" Person.ransack(c: { '0' => { a: ['children_name'], p: 'eq', v: [''] } }).result.to_sql # => SELECT "people".* FROM "people"Both forms drop the condition, so neither contributes a join. This applies at any depth of nested groupings, under the short or long key spellings.
To search for a blank value instead of ignoring it, see
ignore_blank_values.:::note
Before Ransack 5.0 only the shorthand form was pruned. A
c:condition with an empty value still built its attribute and contributed a join, producing aLEFT OUTER JOINwith noWHEREclause to go with it. See #1653.Tweak your routes
resources :people do
collection do
match 'search' => 'people#search', via: [:get, :post], as: :search
end
end
Add a controller action
def search
index
render :index
end
Update your form
<%= search_form_for @q, url: search_people_path,
html: { method: :post } do |f| %>
Once you’ve done so, you can make use of the helpers in Ransack::Helpers::FormBuilder to construct much more complex search forms, such as the one on the demo app (source code here).