Scoping by attribute fields
Clone this wiki locally
The basics of scoping
Scoping in Sunspot is the equivalent of placing conditions on a database query – values against which to compare fields are passed in and processed verbatim. Scopes in Sunspot’s DSL are built using the
Sunspot.search(Post) do with(:published_at).less_than(Time.now) end
This will send a query to Sunspot that is roughly the equivalent of the MySQL query:
SELECT `posts`.* FROM `posts` WHERE `published_at` < NOW();
The argument to the
with method is always the name of an attribute field (e.g., one defined using a type other than
text). The chained method is the name of a restriction; the available restrictions are:
Sunspot also provides shorthand restrictions, wherein a second argument is passed to
with; the restriction type is determined based on the class of the second argument:
- If an Array is passed, an
any_of restriction is created.
- If a Range is passed, a
between restriction is created.
- Otherwise, an
equal_to restriction is created.
For example, the following pairs of restriction calls are equivalent:
with(:blog_id, 1) with(:blog_id).equal_to(1) with(:average_rating, 3.0..5.0) with(:average_rating).between(3.0..5.0) with(:category_ids, [1, 3, 5]) with(:category_ids).any_of([1, 3, 5])
Scoping by empty values
It’s perfectly legal to pass
nil into an
Sunspot.search(Post) do with(:expired_at, nil) end
This will simply scope the search to results who do not have any value indexed in the
expired_at field. Passing nil as a value into any other restriction type is not permitted.
Note that, other than passing
nil into an equality restriction, no restriction will ever match a field that has no value. For instance, neither
with(:published_at).greater_than(Time.now) will match documents which have no
published_at set at all. This will come as a surprise to those who are used to SQL queries but is a natural fact of the way Solr’s range queries work. See the Disjunctions and conjunctions section below for information on how a restriction can be built that matches both values less than a given value and documents that don’t have the field indexed at all.
with method is the
without method, which can be used anywhere the
with method can be used and in the same ways; it simply negates the restriction:
Sunspot.search(Post) do without(:category_ids, 2) end
The above will match all documents who do not have the value 2 in their category_ids field.
Exclusion by object identity
without method also has a special function that does not have an analog in
with, namely to exclude a specific instance from the search results. Passing an object that has an adapter registered with Sunspot will create this special type of restriction:
post = Post.find(params[:id]) Sunspot.search(Post) do without(post) end
The simplest way to combine restrictions is to specify more than one within the search block; this will match documents to whom all of the restrictions apply:
Sunspot.search(Post) do with(:published_at).less_than(Time.now) with(:blog_id, 1) end
The above will match all documents whose published date is in the past, and whose blog_id is 1.
Disjunctions and conjunctions
To combine scopes using OR semantics, use the
any_of method to group restrictions into a disjunction:
Sunspot.search(Post) do any_of do with(:expired_at).greater_than(Time.now) with(:expired_at, nil) end end
The above will match all documents whose expiration date is either in the future, or not set at all.
Inside a disjunction, the
all_of method can be used to nest a set of restrictions combined with AND semantics:
Sunspot.search(Post) do any_of do with(:featured, true) all_of do with(:published_at).less_than(Time.now) with(:expired_at).greater_than(Time.now) end end end
If desired, conjunctions and disjunctions can be nested to indefinite depths.