Queries
On this page
- Condition Syntax
- Field Syntax
- MQL Syntax
- Symbol Operator Syntax
- Fields
- Querying on Defined Fields
- Querying for Raw Values
- Field Aliases
- Embedded Documents
- Logical Operations
- Operator Combinations
and
Behaviorany_of
Behaviornone_of
Behaviornot
Behavior- Incremental Query Construction
- Merge Strategies
- Supported Operator Methods
- Operator Value Expansion
- Query Methods
- elem_match
- Projection
only
without
- Ordering
- Pagination
limit
skip
batch_size
- Finding By
_id
- Additional Query Methods
- Eager Loading
- Regular Expressions
- Conditions On Fields
- Scoping
- Named Scopes
- Default Scopes
- Runtime Default Scope Override
- Class Methods
- Queries + Persistence
- Query Cache
- Enabling Query Cache
- Enabling Query Cache Automatically
- Enabling Query Cache Manually
- Caching the Result of
#first
- Asynchronous Queries
- Configuring asynchronous query execution
Mongoid provides a rich query DSL inspired by ActiveRecord. A trivial query looks as follows:
Band.where(name: "Depeche Mode")
A more complex query utilizing various Mongoid features could be as follows:
Band. where(:founded.gte => "1980-01-01"). in(name: [ "Tool", "Deftones" ]). union. in(name: [ "Melvins" ])
The query methods return Mongoid::Criteria
objects, which are chainable
and lazily evaluated wrappers for MongoDB query language (MQL).
The queries are executed when their result sets are iterated. For example:
# Construct a Criteria object: Band.where(name: 'Deftones') # => #<Mongoid::Criteria # selector: {"name"=>"Deftones"} # options: {} # class: Band # embedded: false> # Evaluate the query and get matching documents: Band.where(name: 'Deftones').to_a # => [#<Band _id: 5ebdeddfe1b83265a376a760, name: "Deftones", description: nil>]
Methods like first
and last
return the individual documents immediately.
Otherwise, iterating a Criteria object with methods like each
or map
retrieves the documents from the server. to_a
can be used to force
execution of a query that returns an array of documents, literally converting
a Criteria object to an Array.
When a query method is called on a Criteria instance, the method returns a new Criteria instance with the new conditions added to the existing conditions:
scope = Band.where(:founded.gte => "1980-01-01") # => #<Mongoid::Criteria # selector: {"founded"=>{"$gte"=>"1980-01-01"}} # options: {} # class: Band # embedded: false> scope.where(:founded.lte => "2020-01-01") # => #<Mongoid::Criteria # selector: {"founded"=>{"$gte"=>"1980-01-01", "$lte"=>"2020-01-01"}} # options: {} # class: Band # embedded: false> scope # => #<Mongoid::Criteria # selector: {"founded"=>{"$gte"=>"1980-01-01"}} # options: {} # class: Band # embedded: false>
Condition Syntax
Mongoid supports three ways of specifying individual conditions:
Field syntax.
MQL syntax.
Symbol operator syntax.
All syntaxes support querying embedded documents using the dot notation. All syntaxes respect field types, if the field being queried is defined in the model class, and field aliases.
The examples in this section use the following model definition:
class Band include Mongoid::Document field :name, type: String field :founded, type: Integer field :m, as: :member_count, type: Integer embeds_one :manager end class Manager include Mongoid::Document embedded_in :band field :name, type: String end
Field Syntax
The simplest querying syntax utilizes the basic Ruby hashes. Keys can be symbols or strings, and correspond to field names in MongoDB documents:
Band.where(name: "Depeche Mode") # => #<Mongoid::Criteria # selector: {"name"=>"Depeche Mode"} # options: {} # class: Band # embedded: false> # Equivalent to: Band.where("name" => "Depeche Mode")
MQL Syntax
An MQL operator may be specified on any field using the hash syntax:
Band.where(founded: {'$gt' => 1980}) # => #<Mongoid::Criteria # selector: {"founded"=>{"$gt"=>1980}} # options: {} # class: Band # embedded: false> # Equivalent to: Band.where('founded' => {'$gt' => 1980})
Symbol Operator Syntax
MQL operators may be specified as methods on symbols for the respective field name, as follows:
Band.where(:founded.gt => 1980) # => #<Mongoid::Criteria # selector: {"founded"=>{"$gt"=>1980}} # options: {} # class: Band # embedded: false>
Fields
Querying on Defined Fields
In order to query on a field, it is not necessary to add the field to the model class definition. However, if a field is defined in the model class, Mongoid will coerce query values to match defined field types when constructing the query:
Band.where(name: 2020, founded: "2020") # => #<Mongoid::Criteria # selector: {"name"=>"2020", "founded"=>2020} # options: {} # class: Band # embedded: false>
Querying for Raw Values
If you'd like to bypass Mongoid's query type coercion behavior and query
directly for the raw-typed value in the database, wrap the query value in
Mongoid::RawValue
class. This can be useful when working with legacy data.
Band.where(founded: Mongoid::RawValue("2020")) # => #<Mongoid::Criteria # selector: {"founded"=>"2020"} # options: {} # class: Band # embedded: false>
Field Aliases
Queries take into account storage field names and field aliases:
Band.where(name: 'Astral Projection') # => #<Mongoid::Criteria # selector: {"n"=>"Astral Projection"} # options: {} # class: Band # embedded: false>
Since id
and _id
fields are aliases, either one can be used for queries:
Band.where(id: '5ebdeddfe1b83265a376a760') # => #<Mongoid::Criteria # selector: {"_id"=>BSON::ObjectId('5ebdeddfe1b83265a376a760')} # options: {} # class: Band # embedded: false>
Embedded Documents
To match values of fields of embedded documents, use the dot notation:
Band.where('manager.name' => 'Smith') # => #<Mongoid::Criteria # selector: {"manager.name"=>"Smith"} # options: {} # class: Band # embedded: false> Band.where(:'manager.name'.ne => 'Smith') # => #<Mongoid::Criteria # selector: {"manager.name"=>{"$ne"=>"Smith"}} # options: {} # class: Band # embedded: false>
Note
Queries always return top-level model instances, even if all of the conditions are referencing embedded documents.
Logical Operations
Mongoid supports and
, or
, nor
and not
logical operations on
Criteria
objects. These methods take one or more hash of conditions
or another Criteria
object as their arguments, with not
additionally
having an argument-free version.
# and with conditions Band.where(label: 'Trust in Trance').and(name: 'Astral Projection') # or with scope Band.where(label: 'Trust in Trance').or(Band.where(name: 'Astral Projection')) # not with conditions Band.not(label: 'Trust in Trance', name: 'Astral Projection') # argument-less not Band.not.where(label: 'Trust in Trance', name: 'Astral Projection')
For backwards compatibility with earlier Mongoid versions, all of the logical operation methods also accept arrays of parameters, which will be flattened to obtain the criteria. Passing arrays to logical operations is deprecated and may be removed in a future version of Mongoid.
The following calls all produce the same query conditions:
# Condition hashes passed to separate and invocations Band.and(name: 'SUN Project').and(member_count: 2) # Multiple condition hashes in the same and invocation Band.and({name: 'SUN Project'}, {member_count: 2}) # Multiple condition hashes in an array - deprecated Band.and([{name: 'SUN Project'}, {member_count: 2}]) # Condition hash in where and a scope Band.where(name: 'SUN Project').and(Band.where(member_count: 2)) # Condition hash in and and a scope Band.and({name: 'SUN Project'}, Band.where(member_count: 2)) # Scope as an array element, nested arrays - deprecated Band.and([Band.where(name: 'SUN Project'), [{member_count: 2}]]) # All produce: # => #<Mongoid::Criteria # selector: {"name"=>"SUN Project", "member_count"=>2} # options: {} # class: Band # embedded: false>
Operator Combinations
As of Mongoid 7.1, logical operators (and
, or
, nor
and not
)
have been changed to have the the same semantics as those of ActiveRecord.
To obtain the semantics of or
as it behaved in Mongoid 7.0 and earlier,
use any_of
which is described below.
When conditions are specified on the same field multiple times, all conditions are added to the criteria:
Band.where(name: 1).where(name: 2).selector # => {"name"=>"1", "$and"=>[{"name"=>"2"}]} Band.where(name: 1).or(name: 2).selector # => {"$or"=>[{"name"=>"1"}, {"name"=>"2"}]}
any_of
, none_of
, nor
and not
behave similarly, with not
producing
different query shapes as described below.
When and
, or
and nor
logical operators are used, they
operate on the criteria built up to that point and its argument.
where
has the same meaning as and
:
# or joins the two conditions Band.where(name: 'Sun').or(label: 'Trust').selector # => {"$or"=>[{"name"=>"Sun"}, {"label"=>"Trust"}]} # or applies only to the first condition, the second condition is added # to the top level as $and Band.or(name: 'Sun').where(label: 'Trust').selector # => {"$or"=>[{"name"=>"Sun"}], "label"=>"Trust"} # Same as previous example - where and and are aliases Band.or(name: 'Sun').and(label: 'Trust').selector # => {"$or"=>[{"name"=>"Sun"}], "label"=>"Trust"} # Same operator can be stacked any number of times Band.or(name: 'Sun').or(label: 'Trust').selector # => {"$or"=>[{"name"=>"Sun"}, {"label"=>"Trust"}]} # The label: Foo condition is added to the top level as $and Band.where(name: 'Sun').or(label: 'Trust').where(label: 'Foo').selector # => {"$or"=>[{"name"=>"Sun"}, {"label"=>"Trust"}], "label"=>"Foo"}
and
Behavior
The and
method will add new simple conditions to the top level of the
criteria, unless the receiving criteria already has a condition on the
respective fields, in which case the conditions will be combined with $and
.
Band.where(label: 'Trust in Trance').and(name: 'Astral Projection').selector # => {"label"=>"Trust in Trance Records", "name"=>"Astral Projection"} Band.where(name: /Best/).and(name: 'Astral Projection').selector # => {"name"=>/Best/, "$and"=>[{"name"=>"Astral Projection"}]}
As of Mongoid 7.1, specifying multiple criteria on the same field with and
combines all criteria so specified, whereas in previous versions of Mongoid
conditions on a field sometimes replaced previously specified conditions on
the same field, depending on which form of and
was used.
or
/nor
Behavior
_----------------------
or
and nor
produce $or
and $nor
MongoDB operators, respectively,
using the receiver and all of the arguments as operands. For example:
Band.where(name: /Best/).or(name: 'Astral Projection') # => {"$or"=>[{"name"=>/Best/}, {"name"=>"Astral Projection"}]} Band.where(name: /Best/).and(name: 'Astral Projection'). or(Band.where(label: /Records/)).and(label: 'Trust').selector # => {"$or"=>[{"name"=>/Best/, "$and"=>[{"name"=>"Astral Projection"}]}, {"label"=>/Records/}], "label"=>"Trust"}
If the only condition on the receiver is another or
/nor
, the new
conditions are added to the existing list:
Band.where(name: /Best/).or(name: 'Astral Projection'). or(Band.where(label: /Records/)).selector # => {"$or"=>[{"name"=>/Best/}, {"name"=>"Astral Projection"}, {"label"=>/Records/}]}
Use any_of
to add a disjunction to a Criteria object while maintaining
all of the conditions built up so far as they are.
any_of
Behavior
any_of
adds a disjunction built from its arguments to the existing
conditions in the criteria. For example:
Band.where(label: /Trust/).any_of({name: 'Astral Projection'}, {name: /Best/}) # => {"label"=>/Trust/, "$or"=>[{"name"=>"Astral Projection"}, {"name"=>/Best/}]}
The conditions are hoisted to the top level if possible:
Band.where(label: /Trust/).any_of({name: 'Astral Projection'}) # => {"label"=>/Trust/, "name"=>"Astral Projection"}
none_of
Behavior
none_of
adds a negated disjunction ("nor") built from its arguments to
the existing conditions in the criteria. For example:
Band.where(label: /Trust/).none_of({name: 'Astral Projection'}, {name: /Best/}) # => {"label"=>/Trust/, "$nor"=>[{"name"=>"Astral Projection"}, {"name"=>/Best/}]}
not
Behavior
not
method can be called without arguments, in which case it will negate
the next condition that is specified. not
can also be called with one
or more hash conditions or Criteria
objects, which will all be negated and
added to the criteria.
# not negates subsequent where Band.not.where(name: 'Best').selector # => {"name"=>{"$ne"=>"Best"}} # The second where is added as $and Band.not.where(name: 'Best').where(label: /Records/).selector # => {"name"=>{"$ne"=>"Best"}, "label"=>/Records/} # not negates its argument Band.not(name: 'Best').selector # => {"name"=>{"$ne"=>"Best"}}
Note
$not
in MongoDB server cannot be used with a string argument.
Mongoid uses $ne
operator to achieve such a negation:
# String negation - uses $ne Band.not.where(name: 'Best').selector # => {"name"=>{"$ne"=>"Best"}} # Regexp negation - uses $not Band.not.where(name: /Best/).selector # => {"name"=>{"$not"=>/Best/}}
Similarly to and
, not
will negate individual conditions for simple
field criteria. For complex conditions and when a field already has a condition
defined on it, since MongoDB server only supports the $not
operator on
a per-field basis rather than globally, Mongoid emulates $not
by using
an {'$and' => [{'$nor' => ...}]}
construct:
# Simple condition Band.not(name: /Best/).selector # => {"name"=>{"$not"=>/Best/}} # Complex conditions Band.where(name: /Best/).not(name: 'Astral Projection').selector # => {"name"=>/Best/, "$and"=>[{"$nor"=>[{"name"=>"Astral Projection"}]}]} # Symbol operator syntax Band.not(:name.ne => 'Astral Projection') # => #<Mongoid::Criteria # selector: {"$and"=>[{"$nor"=>[{"name"=>{"$ne"=>"Astral Projection"}}]}]} # options: {} # class: Band # embedded: false>
If using not
with arrays or regular expressions, please note the
caveats/limitations of $not
stated in the MongoDB server documentation.
Incremental Query Construction
By default, when conditions are added to a query, Mongoid considers each
condition complete and independent from any other conditions potentially
present in the query. For example, calling in
twice adds two separate
$in
conditions:
Band.in(name: ['a']).in(name: ['b']) => #<Mongoid::Criteria selector: {"name"=>{"$in"=>["a"]}, "$and"=>[{"name"=>{"$in"=>["b"]}}]} options: {} class: Band embedded: false>
Some operator methods support building the condition incrementally. In this case, when an condition on a field which uses one of the supported operators is being added, if there already is a condition on the same field using the same operator, the operator expressions are combined according to the specified merge strategy.
Merge Strategies
Mongoid provides three merge strategies:
Override: the new operator instance replaces any existing conditions on the same field using the same operator.
Intersect: if there already is a condition using the same operator on the same field, the values of the existing condition are intersected with the values of the new condition and the result is stored as the operator value.
Union: if there already is a condition using the same operator on the same field, the values of the new condition are added to the values of the existing condition and the result is stored as the operator value.
The following snippet demonstrates all of the strategies, using in
as the
example operator:
Band.in(name: ['a']).override.in(name: ['b']) => #<Mongoid::Criteria selector: {"name"=>{"$in"=>["b"]}} options: {} class: Band embedded: false> Band.in(name: ['a', 'b']).intersect.in(name: ['b', 'c']) => #<Mongoid::Criteria selector: {"name"=>{"$in"=>["b"]}} options: {} class: Band embedded: false> Band.in(name: ['a']).union.in(name: ['b']) => #<Mongoid::Criteria selector: {"name"=>{"$in"=>["a", "b"]}} options: {} class: Band embedded: false>
The strategy is requested by calling override
, intersect
or union
on a Criteria
instance. The requested strategy applies to the next
condition method called on the query. If the next condition method called does
not support merge strategies, the strategy is reset, as shown in the following
example:
Band.in(name: ['a']).union.ne(name: 'c').in(name: ['b']) => #<Mongoid::Criteria selector: {"name"=>{"$in"=>["a"], "$ne"=>"c"}, "$and"=>[{"name"=>{"$in"=>["b"]}}]} options: {} class: Band embedded: false>
Since ne
does not support merge strategies, the union
strategy was
ignored and reset and when in
was invoked the second time there was no
strategy active.
Warning
Merge strategies currently assume the previous condition(s) have been added
to the top level of the query, however this is not always the case
(conditions may be nested under an $and
clause). Using merge strategies
with complex criteria may cause incorrect queries to be constructed.
This misbehavior is intended to be fixed in the future.
Supported Operator Methods
The following operator methods support merge strategies:
all
in
nin
The set of methods may be expanded in future releases of Mongoid. For future compatibility, only invoke a strategy method when the next method call is an operator that supports merge strategies.
Note that the merge strategies are currently only applied when conditions are
added through the designated methods. In the following example merge strategy
is not applied because the second condition is added via where
, not via
in
:
Band.in(foo: ['a']).union.where(foo: {'$in' => 'b'}) => #<Mongoid::Criteria selector: {"foo"=>{"$in"=>["a"]}, "$and"=>[{"foo"=>{"$in"=>"b"}}]} options: {} class: Band embedded: false>
This behavior may change in a future release of Mongoid and should not be relied upon.
In contrast, it does not matter how the existing query was built when a
merge strategy-supporting operator method is invoked. In the following
example, the first condition was added through where
but the strategy
mechanism still applies:
Band.where(foo: {'$in' => ['a']}).union.in(foo: ['b']) => #<Mongoid::Criteria selector: {"foo"=>{"$in"=>["a", "b"]}} options: {} class: Band embedded: false>
Operator Value Expansion
Operator methods that support merge strategies all take Array
as their value
type. Mongoid expands Array
-compatible types, such as a Range
,
when they are used with these operator methods:
Band.in(year: 1950..1960) => #<Mongoid::Criteria selector: {"year"=>{"$in"=>[1950, 1951, 1952, 1953, 1954, 1955, 1956, 1957, 1958, 1959, 1960]}} options: {} class: Band embedded: false>
Additionally, Mongoid has historically wrapped non-Array
values in arrays,
as the following example demonstrates:
Band.in(year: 1950) => #<Mongoid::Criteria selector: {"year"=>{"$in"=>[1950]}} options: {} class: Band embedded: false>
Query Methods
elem_match
This matcher finds documents with array fields where one of the array values matches all of the conditions. For example:
class Band include Mongoid::Document field :name, type: String field :tours, type: Array end aerosmith = Band.create!(name: 'Aerosmith', tours: [ {city: 'London', year: 1995}, {city: 'New York', year: 1999}, ]) Band.elem_match(tours: {city: 'London'}).to_a # => [aerosmith]
elem_match
also works with embedded associations:
class Band include Mongoid::Document field :name, type: String embeds_many :tours end class Tour include Mongoid::Document field :city, type: String field :year, type: Integer embedded_in :band end dm = Band.create!(name: 'Depeche Mode') aerosmith = Band.create!(name: 'Aerosmith') Tour.create!(band: aerosmith, city: 'London', year: 1995) Tour.create!(band: aerosmith, city: 'New York', year: 1999) Band.elem_match(tours: {city: 'London'}).to_a # => [aerosmith]
elem_match
does not work with non-embedded associations because MongoDB
does not have joins - the conditions would be added to the collection
that is the source of a non-embedded association rather than the collection
of the association's target.
elem_match
can also be used with recursively embedded associations,
as the following example shows:
class Tag include Mongoid::Document field :name, type: String recursively_embeds_many end root = Tag.create!(name: 'root') sub1 = Tag.new(name: 'sub1', child_tags: [Tag.new(name: 'subsub1')]) root.child_tags << sub1 root.child_tags << Tag.new(name: 'sub2') root.save! Tag.elem_match(child_tags: {name: 'sub1'}).to_a # => [root] root.child_tags.elem_match(child_tags: {name: 'subsub1'}).to_a # => [sub1]
Projection
Mongoid provides two projection operators: only
and without
.
only
The only
method retrieves only the specified fields from the database. This
operation is sometimes called "projection".
class Band include Mongoid::Document field :name, type: String field :label, type: String embeds_many :tours end class Tour include Mongoid::Document field :city, type: String field :year, type: Integer embedded_in :band end band = Band.only(:name).first
Attempting to reference attributes which have not been loaded results in
Mongoid::Errors::AttributeNotLoaded
.
band.label #=> raises Mongoid::Errors::AttributeNotLoaded
Even though Mongoid currently allows writing to attributes that have not been loaded, such writes will not be persisted (MONGOID-4701) and should therefore be avoided.
only
can also be used with embedded associations:
band = Band.only(:name, 'tours.year').last # => #<Band _id: 5c59afb1026d7c034dba46ac, name: "Aerosmith"> band.tours.first # => #<Tour _id: 5c59afdf026d7c034dba46af, city: nil, year: 1995>
Note
Server versions 4.2 and lower allowed projecting both an association and the association's fields in the same query, as follows:
band = Band.only(:tours, 'tours.year').last
The most recent projection specification overrides the earlier one. For example, the above query was equivalent to:
band = Band.only('tours.year').last
Server versions 4.4 and higher prohibit specifying an association and its fields in projection in the same query.
only
can be specified with referenced associations (has_one, has_many,
has_and_belongs_to_many) but is currently ignored for referenced associations -
all fields of referenced associations will be loaded
(MONGOID-4704).
Note that if a document has has_one
or has_and_belongs_to_many
associations,
the fields with foreign keys must be included in the list of attributes
loaded with only
for those associations to be loaded. For example:
class Band include Mongoid::Document field :name, type: String has_and_belongs_to_many :managers end class Manager include Mongoid::Document has_and_belongs_to_many :bands end band = Band.create!(name: 'Astral Projection') band.managers << Manager.new Band.where(name: 'Astral Projection').only(:name).first.managers # => [] Band.where(name: 'Astral Projection').only(:name, :manager_ids).first.managers # => [#<Manager _id: 5c5dc2f0026d7c1730969843, band_ids: [BSON::ObjectId('5c5dc2f0026d7c1730969842')]>]
without
The opposite of only
, without
causes the specified fields to be omitted:
Band.without(:name) # => # #<Mongoid::Criteria # selector: {} # options: {:fields=>{"name"=>0}} # class: Band # embedded: false>
Because Mongoid requires the _id
field for various operations, it (as well
as its id
alias) cannot be omitted via without
:
Band.without(:name, :id) # => # #<Mongoid::Criteria # selector: {} # options: {:fields=>{"name"=>0}} # class: Band # embedded: false> Band.without(:name, :_id) # => # #<Mongoid::Criteria # selector: {} # options: {:fields=>{"name"=>0}} # class: Band # embedded: false>
Ordering
Mongoid provides the order
method on Criteria
objects and its alias,
order_by
, to specify the ordering of documents. These methods take a
hash indicating which fields to order the documents by, and whether to use
ascending or descending order for each field.
Band.order(name: 1) # => #<Mongoid::Criteria # selector: {} # options: {:sort=>{"name"=>1}} # class: Band # embedded: false> Band.order_by(name: -1, description: 1) # => #<Mongoid::Criteria # selector: {} # options: {:sort=>{"name"=>-1, "description"=>1}} # class: Band # embedded: false> Band.order_by(name: :desc, description: 'asc') # => #<Mongoid::Criteria # selector: {} # options: {:sort=>{"name"=>-1, "description"=>1}} # class: Band # embedded: false>
The direction may be specified as integers 1
and -1
for ascending
and descending, respectively, or as symbols :asc
and :desc
, or as
strings "asc"
and "desc"
.
Alternatively, order
accepts an array of two-element arrays specifying
the ordering. Field names and directions may be strings or symbols.
Band.order([['name', 'desc'], ['description', 'asc']]) Band.order([[:name, :desc], [:description, :asc]])
Another way of providing the order is to use #asc
and #desc
methods
on symbols, as follows:
Band.order(:name.desc, :description.asc)
The arguments can be provided as a string using SQL syntax:
Band.order('name desc, description asc')
Finally, there are asc
and desc
methods that can be used instead of
order
/order_by
:
Band.asc('name').desc('description') # => #<Mongoid::Criteria selector: {} options: {:sort=>{"name"=>1, "description"=>-1}} class: Band embedded: false>
order
calls can be chained, in which case the oldest calls define the
most significant criteria and the newest calls define the least significant
ones (since in Ruby hashes maintain the order of their keys):
Band.order('name desc').order('description asc') # => #<Mongoid::Criteria selector: {} options: {:sort=>{"name"=>-1, "description"=>1}} class: Band embedded: false>
This can sometimes lead to surprising results if there are scopes, including
the default scope, that use order
/order_by
. For example, in the
following snippet bands are ordered by name first because the order in the
default scope takes precedence over the order given in the query, due to
the default scope being evaluated first:
class Band include Mongoid::Document field :name, type: String field :year, type: Integer default_scope -> { order(name: :asc) } end Band.order(year: :desc) # => #<Mongoid::Criteria selector: {} options: {:sort=>{"name"=>1, "year"=>-1}} class: Band embedded: false>
Pagination
Mongoid provides the pagination operators limit
, skip
, and batch_size
on Criteria
.
limit
limit
sets the total number of documents to be returned by a query:
Band.limit(5) # => # #<Mongoid::Criteria # selector: {} # options: {:limit=>5} # class: Band # embedded: false>
skip
skip
(alias: offset
) sets the number of query results to skip
before returning documents. The limit
value, if specified, will be applied
after documents are skipped. When performing pagination, skip
is recommended
to be combined with ordering to ensure consistent results.
Band.skip(10) # => # #<Mongoid::Criteria # selector: {} # options: {:skip=>10} # class: Band # embedded: false>
batch_size
When executing large queries, or when iterating over query results with an enumerator method such as
Criteria#each
, Mongoid automatically uses the MongoDB getMore command to load results in batches.
The default batch_size
is 1000, however you may set it explicitly:
Band.batch_size(500) # => # #<Mongoid::Criteria # selector: {} # options: {:batch_size=>500} # class: Band # embedded: false>
Finding By _id
Mongoid provides the find
method on Criteria
objects to find documents
by their _id
values:
Band.find('5f0e41d92c97a64a26aabd10') # => #<Band _id: 5f0e41d92c97a64a26aabd10, name: "Juno Reactor">
The find
method performs type conversion, if necessary, of the argument
to the type declared in the model being queried for the _id
field.
By default, the _id
type is BSON::ObjectId
, thus the query above
is equivalent to:
Band.find(BSON::ObjectId.from_string('5f0e41d92c97a64a26aabd10')) # => #<Band _id: 5f0e41d92c97a64a26aabd10, name: "Juno Reactor">
Note
When querying collections directly using the driver, type conversion is not automatically performed:
Band.collection.find(_id: BSON::ObjectId.from_string('5f0e41d92c97a64a26aabd10')).first # => {"_id"=>BSON::ObjectId('5f0e41d92c97a64a26aabd10'), "name"=>"Juno Reactor"} Band.collection.find(_id: '5f0e41d92c97a64a26aabd10').first # => nil
The find
method can accept multiple arguments, or an array of arguments.
In either case each of the arguments or array elements is taken to be an _id
value, and documents with all of the specified _id
values are returned in
an array:
Band.find('5f0e41d92c97a64a26aabd10', '5f0e41b02c97a64a26aabd0e') # => [#<Band _id: 5f0e41b02c97a64a26aabd0e, name: "SUN Project", description: nil, likes: nil>, #<Band _id: 5f0e41d92c97a64a26aabd10, name: "Juno Reactor", description: nil, likes: nil>] Band.find(['5f0e41d92c97a64a26aabd10', '5f0e41b02c97a64a26aabd0e']) # => [#<Band _id: 5f0e41b02c97a64a26aabd0e, name: "SUN Project", description: nil, likes: nil>, #<Band _id: 5f0e41d92c97a64a26aabd10, name: "Juno Reactor", description: nil, likes: nil>]
If the same _id
value is given more than once, the corresponding document
is only returned once:
Band.find('5f0e41b02c97a64a26aabd0e', '5f0e41b02c97a64a26aabd0e') # => [#<Band _id: 5f0e41b02c97a64a26aabd0e, name: "SUN Project", description: nil, likes: nil>]
The documents returned are not ordered, and may be returned in a different
order from the order of provided _id
values, as illustrated in the above
examples.
If any of the _id
values are not found in the database, the behavior of
find
depends on the value of the raise_not_found_error
configuration
option. If the option is set to true
, find
raises
Mongoid::Errors::DocumentNotFound
if any of the _id
s are not found.
If the option is set to false
and find
is given a single _id
to
find and there is no matching document, find
returns nil
. If the
option is set to false
and find
is given an array of ids to find
and some are not found, the return value is an array of documents that were
found (which could be empty if no documents were found at all).
Additional Query Methods
Mongoid also has some helpful methods on criteria.
Operation | Example | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Get the total number of documents matching a filter, or the total number of documents in a collection. Note this will always hit the database for the count. As of Mongoid 7.2, the |
| ||||||||||||||||
Get an approximate number of documents in the collection using the
collection metadata. The |
| ||||||||||||||||
Get a list of distinct values for a single field. Note this will always hit the database for the distinct values. This method accepts the dot notation, thus permitting referencing fields in embedded associations. This method respects :ref:`field aliases <field-aliases>`, including those defined in embedded documents. |
| ||||||||||||||||
Iterate over all matching documents in the criteria. |
| ||||||||||||||||
Determine if any matching documents exist. Will return true if there are 1 or more.
|
| ||||||||||||||||
Get the fifth document for the given criteria. This method automatically adds a sort on _id if no sort is given. |
| ||||||||||||||||
Get the fifth document for the given criteria, or raise an error if none exist. This method automatically adds a sort on _id if no sort is given. |
| ||||||||||||||||
Find a document by the provided attributes. If not found,
raise an error or return nil depending on the value of the
|
| ||||||||||||||||
Find a document by the provided attributes, and if not found create and return a newly persisted one. Note that attributes provided in the arguments to this method will override any set in ``create_with``. |
| ||||||||||||||||
Find a document by the provided attributes, and if not found return a new one. |
| ||||||||||||||||
Finds a single document given the provided criteria. Get a list of documents by passing in a limit argument. This method automatically adds a sort on _id. This can cause performance issues, so if the sort is undesirable, Criteria#take can be used instead. |
| ||||||||||||||||
Finds a single document given the provided criteria, or raises an error if none are found. This method automatically adds a sort on _id if no sort is given. This can cause performance issues, so if the sort is undesirable, Criteria#take! can be used instead. |
| ||||||||||||||||
Find the first document by the provided attributes, and if not found create and return a newly persisted one. |
| ||||||||||||||||
Find the first document by the provided attributes, and if not found
create and return a newly persisted one using |
| ||||||||||||||||
Find the first document by the provided attributes, and if not found return a new one. |
| ||||||||||||||||
Find documents for a provided JavaScript expression, optionally with
the specified variables added to the evaluation scope. The scope
argument is supported in MongoDB 4.2 and lower.
Prefer $expr over |
| ||||||||||||||||
Get the fourth document for the given criteria. This method automatically adds a sort on _id if no sort is given. |
| ||||||||||||||||
Get the fourth document for the given criteria, or raise an error if none exist. This method automatically adds a sort on _id if no sort is given. |
| ||||||||||||||||
Same as count but caches subsequent calls to the database |
| ||||||||||||||||
Get the values from one document for the provided fields. Returns nil for unset fields and for non-existent fields. This method does not apply a sort to the documents, so it will not necessarily return the values from the first document. This method accepts the dot notation, thus permitting referencing fields in embedded associations. This method respects :ref:`field aliases <field-aliases>`, including those defined in embedded documents. |
| ||||||||||||||||
Get all the values for the provided field. Returns nil for unset fields and for non-existent fields. This method accepts the dot notation, thus permitting referencing fields in embedded associations. This method respects :ref:`field aliases <field-aliases>`, including those defined in embedded documents. |
| ||||||||||||||||
Sets the read preference for the criteria. |
| ||||||||||||||||
Get the second document for the given criteria. This method automatically adds a sort on _id if no sort is given. |
| ||||||||||||||||
Get the second document for the given criteria, or raise an error if none exist. This method automatically adds a sort on _id if no sort is given. |
| ||||||||||||||||
Get the second to last document for the given criteria. This method automatically adds a sort on _id if no sort is given. |
| ||||||||||||||||
Get the second to last document for the given criteria, or raise an error if none exist. This method automatically adds a sort on _id if no sort is given. |
| ||||||||||||||||
Get a list of n documents from the database or just one if no parameter is provided. This method does not apply a sort to the documents, so it can return different document(s) than #first and #last. |
| ||||||||||||||||
Get a document from the database or raise an error if none exist. This method does not apply a sort to the documents, so it can return different document(s) than #first and #last. |
| ||||||||||||||||
Get a mapping of values to counts for the provided field. This method accepts the dot notation, thus permitting referencing fields in embedded associations. This method respects :ref:`field aliases <field-aliases>`, including those defined in embedded documents. |
| ||||||||||||||||
Get the third document for the given criteria. This method automatically adds a sort on _id if no sort is given. |
| ||||||||||||||||
Get the third document for the given criteria, or raise an error if none exist. This method automatically adds a sort on _id if no sort is given. |
| ||||||||||||||||
Get the third to last document for the given criteria. This method automatically adds a sort on _id if no sort is given. |
| ||||||||||||||||
Get the third to last document for the given criteria, or raise an error if none exist. This method automatically adds a sort on _id if no sort is given. |
|
Eager Loading
Mongoid provides a facility to eager load documents
from associations to prevent the n+1 issue when
iterating over documents with association access. Eager loading is supported on
all associations with the exception of polymorphic belongs_to
associations.
class Band include Mongoid::Document has_many :albums end class Album include Mongoid::Document belongs_to :band end Band.includes(:albums).each do |band| p band.albums.first.name # Does not hit the database again. end
Regular Expressions
MongoDB, and Mongoid, allow querying documents by regular expressions.
Given the following model definitions:
class Band include Mongoid::Document field :name, type: String field :description, type: String end Band.create!(name: 'Sun Project', description: "Sun\nProject")
... we can query using simple Ruby regular expressions in a natural way:
Band.where(name: /project/i).first # => #<Band _id: 5dc9f7d5ce4ef34893354323, name: "Sun Project", description: "Sun\nProject">
It is also possible to query using PCRE syntax by constructing
BSON::Regexp::Raw
objects explicitly:
Band.where(description: /\AProject/).first # => #<Band _id: 5dc9f7d5ce4ef34893354323, name: "Sun Project", description: "Sun\nProject"> Band.where(description: BSON::Regexp::Raw.new('^Project')).first # => nil Band.where(description: BSON::Regexp::Raw.new('^Project', 'm')).first # => #<Band _id: 5dc9f7d5ce4ef34893354323, name: "Sun Project", description: "Sun\nProject">
Conditions On Fields
When a condition uses a field defined in the model, the value being specified
in the condition is converted according to the rules of the field, if any.
For example, consider the following model definition that contains a Time
field, a Date
field and an implicit Object
field, and also
intentionally does not define a field called deregistered_at
:
class Voter include Mongoid::Document field :born_on, type: Date field :registered_at, type: Time field :voted_at end
Queries on born_on
and registered_at
fields using Date
and Time
values, respectively, are straightforward:
Voter.where(born_on: Date.today).selector # => {"born_on"=>2020-12-18 00:00:00 UTC} Voter.where(registered_at: Time.now).selector # => {"registered_at"=>2020-12-19 04:33:36.939788067 UTC}
But, note the differences in behavior when providing a Date
instance
in all possible scenarios:
Voter.where(born_on: Date.today).selector # => {"born_on"=>2020-12-18 00:00:00 UTC} Voter.where(registered_at: Date.today).selector # => {"registered_at"=>2020-12-18 00:00:00 -0500} Voter.where(voted_at: Date.today).selector # => {"voted_at"=>Fri, 18 Dec 2020} Voter.where(deregistered_at: Date.today).selector # => {"deregistered_at"=>2020-12-18 00:00:00 UTC}
When using the registered_at
field which is of type Time
, the date
was interpreted to be in local time (as per the configured time zone). When using the born_on
field which is of type Date
,
the date was interpreted to be in UTC. When using the voted_at
field
which was defined without a type (hence implicitly as an Object
),
the date was used unmodified in the constructed query. When using a
nonexistent field deregistered_at
the date was interpreted to be in UTC
and converted to a time, matching the behavior of querying a Date
field.
Scoping
Scopes provide a convenient way to reuse common criteria with more business domain style syntax.
Named Scopes
Named scopes are simply criteria defined at class load that are referenced by a provided name. Just like normal criteria, they are lazy and chainable.
class Band include Mongoid::Document field :country, type: String field :genres, type: Array scope :english, ->{ where(country: "England") } scope :rock, ->{ where(:genres.in => [ "rock" ]) } end Band.english.rock # Get the English rock bands.
Named scopes can take procs and blocks for accepting parameters or extending functionality.
class Band include Mongoid::Document field :name, type: String field :country, type: String field :active, type: Boolean, default: true scope :named, ->(name){ where(name: name) } scope :active, ->{ where(active: true) do def deutsch tap do |scope| scope.selector.store("origin" => "Deutschland") end end end } end Band.named("Depeche Mode") # Find Depeche Mode. Band.active.deutsch # Find active German bands.
By default, Mongoid allows defining a scope that would shadow an existing class method, as the following example shows:
class Product include Mongoid::Document def self.fresh true end scope :fresh, ->{ where(fresh: true) } end
To have Mongoid raise an error when a scope would overwrite an existing class
method, set the scope_overwrite_exception
configuration option to true
.
Default Scopes
Default scopes can be useful when you find yourself applying the same criteria to most queries, and wish to specify these criteria as the default. Default scopes are procs that return criteria objects.
class Band include Mongoid::Document field :name, type: String field :active, type: Boolean default_scope ->{ where(active: true) } end Band.each do |band| # All bands here are active. end
Specifying a default scope also initializes the fields of new models to the values given in the default scope, if the values are simple literals:
class Band include Mongoid::Document field :name, type: String field :active, type: Boolean field :num_tours, type: Integer default_scope ->{ where(active: true, num_tours: {'$gt' => 1}) } end # active is set, num_tours is not set Band.new # => #<Band _id: 5c3f7452ce4ef378295ca5f5, name: nil, active: true, num_tours: nil>
Note that if a default value is provided both in the field definition and in the default scope, the value in the default scope takes precedence:
class Band include Mongoid::Document field :name, type: String field :active, type: Boolean, default: true default_scope ->{ where(active: false) } end Band.new # => #<Band _id: 5c3f74ddce4ef3791abbb088, name: nil, active: false>
Because a default scope initializes fields in new models as just described, defining a default scope with a dotted key and a simple literal value, while possible, is not recommended:
class Band include Mongoid::Document field :name, type: String field :tags, type: Hash default_scope ->{ where('tags.foo' => 'bar') } end Band.create! # => Created document: {"_id"=>BSON::ObjectId('632de48f3282a404bee1877b'), "tags.foo"=>"bar"} Band.create!(tags: { 'foo' => 'bar' }) # => Created document: {"_id"=>BSON::ObjectId('632de4ad3282a404bee1877c'), "tags.foo"=>"bar", "tags"=>{"foo"=>"bar"}} Band.all.to_a # => [ #<Band _id: 632de4ad3282a404bee1877c, tags: {"foo"=>"bar"}> ]
Mongoid 8 allows dotted keys to be used in Mongoid, and when creating a document, the scope is added as a dotted key in the attributes:
Band.new.attribute # => {"_id"=>BSON::ObjectId('632de97d3282a404bee1877d'), "tags.foo"=>"bar"}
Whereas when querying, Mongoid looks for an embedded document:
Band.create! # => Created document: {"_id"=>BSON::ObjectId('632de48f3282a404bee1877b'), "tags.foo"=>"bar"} Band.where # => #<Mongoid::Criteria selector: {"tags.foo"=>"bar"} options: {} class: Band embedded: false> # This looks for something like: { tags: { "foo" => "bar" } } Band.count # => 0
A workaround is to define the default scope as a complex query:
class Band include Mongoid::Document field :name, type: String field :tags, type: Hash default_scope ->{ where('tags.foo' => {'$eq' => 'bar'}) } end Band.create!(tags: { hello: 'world' }) Band.create!(tags: { foo: 'bar' }) # does not add a "tags.foo" dotted attribute Band.count # => 1
You can tell Mongoid not to apply the default scope by using
unscoped
, which can be inline or take a block.
Band.unscoped.where(name: "Depeche Mode") Band.unscoped do Band.where(name: "Depeche Mode") end
You can also tell Mongoid to explicitly apply the default scope again later to always ensure it's there.
Band.unscoped.where(name: "Depeche Mode").scoped
If you are using a default scope on a model that is part of an association, you must reload the association to have scoping reapplied. This is important to note if you change a value of a document in the association that would affect its visibility within the scoped association.
class Label include Mongoid::Document embeds_many :bands end class Band include Mongoid::Document field :active, default: true embedded_in :label default_scope ->{ where(active: true) } end label.bands.push(band) label.bands # [ band ] band.update_attribute(:active, false) label.bands # [ band ] Must reload. label.reload.bands # []
Note
After the default scope is applied, it is no longer distinguished from
other query conditions. This can lead to surprising behavior when using
or
and nor
operators in particular:
class Band include Mongoid::Document field :name field :active field :touring default_scope ->{ where(active: true) } end Band.where(name: 'Infected Mushroom') # => # #<Mongoid::Criteria # selector: {"active"=>true, "name"=>"Infected Mushroom"} # options: {} # class: Band # embedded: false> Band.where(name: 'Infected Mushroom').or(touring: true) # => # #<Mongoid::Criteria # selector: {"$or"=>[{"active"=>true, "name"=>"Infected Mushroom"}, {"touring"=>true}]} # options: {} # class: Band # embedded: false> Band.or(touring: true) # => # #<Mongoid::Criteria # selector: {"$or"=>[{"active"=>true}, {"touring"=>true}]} # options: {} # class: Band # embedded: false>
In the last example, you might expect the two conditions
(active: true
and touring: true
) to be combined with an $and
,
but because the Band
class already has the scope applied to it,
it becomes one of the disjunction branches of the or
.
Runtime Default Scope Override
You can use the with_scope
method to change the default scope in a block
at runtime:
class Band include Mongoid::Document field :country, type: String field :genres, type: Array scope :english, ->{ where(country: "England") } end criteria = Band.with_scope(Band.english) do Band.all end criteria # => # #<Mongoid::Criteria # selector: {"country"=>"England"} # options: {} # class: Band # embedded: false>
Note
If with_scope calls are nested, when the nested with_scope block completes
Mongoid 7 sets the current scope to nil instead of the parent scope.
Mongoid 8 will set the current scope to the correct parent scope.
To get Mongoid 8 behavior in Mongoid 7.4 and higher, set the
Mongoid.broken_scoping
global option to false.
Class Methods
Class methods on models that return criteria objects are also treated like scopes, and can be chained as well.
class Band include Mongoid::Document field :name, type: String field :active, type: Boolean, default: true def self.active where(active: true) end end Band.active
Queries + Persistence
Mongoid supports persistence operations off of criteria in a light capacity for when you want to expressively perform multi document inserts, updates, and deletion.
Warning
Criteria ordering and pagination conditions, including order
, limit
,
offset
, and batch_size
, will be ignored on the following operations.
Operation | Example | ||
---|---|---|---|
Create a newly persisted document. |
| ||
Create a newly persisted document and raise an exception on validation failure. |
| ||
Create a new (unsaved) document. |
| ||
Update attributes of the first matching document. |
| ||
Update attributes of all matching documents. |
| ||
Perform an $addToSet on all matching documents. |
| ||
Perform a $bit on all matching documents. |
| ||
Perform an $inc on all matching documents. |
| ||
Perform a $pop on all matching documents. |
| ||
Perform a $pull on all matching documents. |
| ||
Perform a $pullAll on all matching documents. |
| ||
Perform a $push on all matching documents. |
| ||
Perform a $push with $each on all matching documents. |
| ||
Perform a $rename on all matching documents. |
| ||
Perform a $set on all matching documents. |
| ||
Perform a $unset on all matching documents. |
| ||
Deletes all matching documents in the database. |
| ||
Deletes all matching documents in the database while running callbacks for all. This loads all documents into memory and can be an expensive operation. |
|
Query Cache
The Ruby MongoDB driver versions 2.14 and above provide query caching functionality. When enabled, the query cache saves the results of previously executed find and aggregation queries and reuses them in the future instead of performing the queries again, thus increasing application performance and reducing database load.
Please review the driver query cache documentation for details about the driver's query cache behavior.
The rest of this section assumes that driver 2.14.0 or later is being used.
Enabling Query Cache
The query cache may be enabled by using the driver's namespace or Mongoid's namespace.
Enabling Query Cache Automatically
The MongoDB Ruby Driver provides middleware to automatically enable the query cache for Rack web requests and ActiveJob job runs. Please see the Query Cache Rack Middleware section on the configuration page for instructions.
Note that the Query Cache Middleware does not apply to code executed outside web requests and/or jobs.
Enabling Query Cache Manually
To enable the Query Cache manually for a code segment, use:
Mongo::QueryCache.cache do # ... end
The Query Cache can also be explicitly enabled and disabled, although we recommend to use the block form described above:
begin Mongo::QueryCache.enabled = true # ... ensure Mongo::QueryCache.enabled = false end
Caching the Result of #first
Calling the first
method on a model class imposes an ascending sort by
the _id
field on the underlying query. This may produce unexpected behavior
with query caching.
For example, when calling all
on a model class and then first
,
one would expect the second query to use the cached results from the first.
However, because of the sort imposed on the second query, both methods
will query the database and separately cache their results.
Band.all.to_a #=> Queries the database and caches the results Band.first #=> Queries the database again because of the sort
To use the cached results, call all.to_a.first
on the model class.
Asynchronous Queries
Mongoid allows running database queries asynchronously in the background. This can be beneficial when there is a need to get documents from different collections.
In order to schedule an asynchronous query call the load_async
method on a
Criteria
:
class PagesController < ApplicationController def index @active_bands = Band.where(active: true).load_async @best_events = Event.best.load_async @public_articles = Article.where(public: true).load_async end end
In the above example three queries will be scheduled for asynchronous execution. Results of the queries can be later accessed as usual:
<ul> <%- @active_bands.each do -%> <li><%= band.name %></li> <%- end -%> </ul>
Even if a query is scheduled for asynchronous execution, it might be executed synchronously on the caller's thread. There are three possible scenarios depending on when the query results are being accessed:
If the scheduled asynchronous task has been already executed, the results are returned.
If the task has been started, but not finished yet, the caller's thread blocks until the task is finished.
If the task has not been started yet, it is removed from the execution queue, and the query is executed synchronously on the caller's thread.
Note
Even though load_async
method returns a Criteria
object, you should not
do any operations on this object except accessing query results. The query is
scheduled for execution immediately after calling load_async
, therefore
later changes to the Criteria
object may not be applied.
Configuring asynchronous query execution
Asynchronous queries are disabled by default. When asynchronous queries are
disabled, load_async
will execute the query immediately on the current thread,
blocking as necessary. Therefore, calling load_async
on criteria in this case
is roughly the equivalent of calling to_a
to force query execution.
In order to enable asynchronous query execution, the following config options must be set:
development: ... options: # Execute asynchronous queries using a global thread pool. async_query_executor: :global_thread_pool # Number of threads in the pool. The default is 4. # global_executor_concurrency: 4