Navigation

Upgrading Mongoid

This page describes significant changes and improvements in Mongoid releases. The complete list of releases is available on GitHub and in JIRA; please consult GitHub releases for detailed release notes and JIRA for the complete list of issues fixed in each release, including bug fixes.

Upgrading to Mongoid 7.3

The following sections describe significant changes in Mongoid 7.3.

::Boolean Removed

Breaking change: Mongoid 7.3 removes the global ::Boolean class.

This change should have no impact on classes that simply use Boolean fields, as the Boolean class is aliased from Mongoid::Fields (which is included in Mongoid::Document). The following field definition continues to work in 7.3 as it did in 7.2:

class User
  include Mongoid::Document

  field :verified, type: Boolean
end

However, code that is not executed in the context of a class including Mongoid::Document may need to explicitly qualify Boolean references. The following snippet fails with Mongoid 7.3 due to Boolean being unqualified:

class User
  include Mongoid::Document
end

User.field :verified, type: Boolean

To fix it, use the fully-qualified Mongoid::Boolean class:

User.field :verified, type: Mongoid::Boolean

Note that class_eval is executed in the scope of the caller, not in the scope of the class being modified. Thus even when using class_eval it is necessary to fully qualify Mongoid::Boolean:

User.class_eval do
  field :verified, type: Mongoid::Boolean
end

Additionally, in Mongoid 7.2 ::Boolean and Mongoid::Boolean were different classes. In Mongoid 7.3 there is only one class which is Mongoid::Boolean.

It is possible to restore the global ::Boolean class by executing in your application:

Boolean = Mongoid::Boolean

Note that this aliases Mongoid::Boolean to ::Boolean such that there is still only a single Boolean class:

# With Mongoid 7.3:
Boolean = Mongoid::Boolean
Boolean == Mongoid::Boolean
# => true

# With Mongoid 7.2:
Boolean == Mongoid::Boolean
# => false

Selector Key Stringification

Minor change: Mongoid now converts symbol keys to string keys in the Criteria selectors. This applies to operators as well as hash literals.

Mongoid 7.3 behavior:

Band.and(year: {'$in': [2020]})
# =>
# #<Mongoid::Criteria
#   selector: {"year"=>{"$in"=>[2020]}}
#   options:  {}
#   class:    Band
#   embedded: false>

Band.where(tag: {city: 1})
# =>
# #<Mongoid::Criteria
#   selector: {"tag"=>{"city"=>1}}
#   options:  {}
#   class:    Band
#   embedded: false>

Mongoid 7.2 behavior:

Band.and(year: {'$in': [2020]})
# =>
# #<Mongoid::Criteria
#   selector: {"year"=>{:$in=>[2020]}}
#   options:  {}
#   class:    Band
#   embedded: false>

Band.where(tag: {city: 1})
# =>
# #<Mongoid::Criteria
#   selector: {"tag"=>{:city=>1}}
#   options:  {}
#   class:    Band
#   embedded: false>

Condition Combination Using $eq

Minor change: when using the where, and, or, and nor methods on Criteria objects and providing multiple conditions on the same field in the same argument using the symbol operator syntax, conditions may be combined using $eq instead of $and.

Mongoid 7.3 behavior:

Band.where(year: 2020, :year.gt => 1960)
# =>
# #<Mongoid::Criteria
#   selector: {"year"=>{"$eq"=>2020, "$gt"=>1960}}
#   options:  {}
#   class:    Band
#   embedded: false>

Mongoid 7.2 behavior:

Band.where(year: 2020, :year.gt => 1960)
# =>
# #<Mongoid::Criteria
#   selector: {"year"=>2020, "$and"=>[{"year"=>{"$gt"=>1960}}]}
#   options:  {}
#   class:    Band
#   embedded: false>

When using the not method with multiple conditions provided in the same argument, the conditions are kept together and negated as a group.

Mongoid 7.3 behavior:

Band.not(year: 2020, :year.gt => 1960)
# =>
# #<Mongoid::Criteria
#   selector: {"$and"=>[{"$nor"=>[{"year"=>{"$eq"=>2020, "$gt"=>1960}}]}]}
#   options:  {}
#   class:    Band
#   embedded: false>

Mongoid 7.2 behavior:

Band.not(year: 2020, :year.gt => 1960)
# =>
# #<Mongoid::Criteria
#   selector: {"year"=>{"$ne"=>2020}, "$and"=>[{"$nor"=>[{"year"=>{"$gt"=>1960}}]}]}
#   options:  {}
#   class:    Band
#   embedded: false>

New Embedded Matching Operators

Mongoid 7.3 adds support for bitwise operators, $comment, $mod and $type operators when embedded matching.

Unaliasing id Field

It is now possible to remove the id alias in models, to make id a regular field.

Upgrading to Mongoid 7.2

The following sections describe significant changes in Mongoid 7.2.

Embedded Document Matching

Breaking change: In Mongoid 7.2 embedded matchers were largely rewritten. Most queries should produce the same results as in previous versions of Mongoid but a number of cases changed behavior to make Mongoid behave the same way MongoDB server behaves. Note that the changes, for the most part, affect queries using manually constructed MQL expressions; Mongoid query methods generate MQL that is generally not affected by the changes in embedded matching.

To illustrate the differences, the examples below use the following model definitions:

class Job
  include Mongoid::Document

  embeds_many :tasks
end

class Task
  include Mongoid::Document
  include Mongoid::Attributes::Dynamic

  embedded_in :job
end

job = Job.new(tasks: [
  Task.new(name: 'Clean house', pattern: /test/, hours: 12),
])

The changes in behavior are as follows:

$eq and Regular Expression Values

$eq now performs exact matching when given regular a expression argument. Previously, both operators used regular expression matching, which caused Mongoid to not find documents where the field being matched was a regular expression itself:

job.tasks.where(name: {'$eq': /house/}).first
# Mongoid 7.2:
# => nil
# Mongoid 7.1:
# => #<Task _id: 5ef8dc3e2c97a645ec86bb33, name: "Clean house", pattern: /test/, hours: 12>

job.tasks.where(pattern: {'$eq': /test/}).first
# Mongoid 7.2:
# => #<Task _id: 5ef8dc3e2c97a645ec86bb33, name: "Clean house", pattern: /test/, hours: 12>
# Mongoid 7.1:
# => nil

To perform a regular expression match, provide the regular expression directly without an operator:

job.tasks.where(name: /house/).first
# Mongoid 7.2 and 7.1:
# => #<Task _id: 5ef8dc3e2c97a645ec86bb33, name: "Clean house", pattern: /test/, hours: 12>

$ne and Regular Expression Values

$ne no longer accepts regular expression arguments, which is the behavior of MongoDB server:

job.tasks.where(name: {'$ne': /apartment/}).first
# Mongoid 7.2: raises Mongoid::Errors::InvalidQuery
# Mongoid 7.1:
# => #<Task _id: 5ef8dc3e2c97a645ec86bb33, name: "Clean house", pattern: /test/, hours: 12>

To perform a negated regular expression match, use the not method on a symbol key:

job.tasks.where(:name.not => /house/).first
# Mongoid 7.2 and 7.1:
# => nil
job.tasks.where(:name.not => /office/).first
# Mongoid 7.2 and 7.1:
# => #<Task _id: 5ef8dc3e2c97a645ec86bb33, name: "Clean house", pattern: /test/, hours: 12>

$eq, $ne and Range Values

Range values are no longer accepted by $eq and $ne operators. This change should not be visible to applications since Mongoid generally expands Range values to a $gte/$lte pair before they get to the embedded matchers.

To query using a range, use the following syntax which works in Mongoid 7.2 as well as previous versions:

job.tasks.where(hours: 10..15)
# =>
# #<Mongoid::Criteria
#   selector: {"hours"=>{"$gte"=>10, "$lte"=>15}}
#   options:  {}
#   class:    Task
#   embedded: true>

job.tasks.where(hours: 10..15).first
# => #<Task _id: 5ef8dd4c2c97a6465e8a4ffa, name: "Clean house", pattern: /test/, hours: 12>

Mongoid 7.1 accepted Range values as operator arguments, but generated queries that would never match documents. For example, the following expression was accepted but never matched any documents:

job.tasks.where(hours: {'$in': 10..15})
# =>
# #<Mongoid::Criteria
#   selector: {"hours"=>{:$in=>{"$gte"=>10, "$lte"=>15}}}
#   options:  {}
#   class:    Task
#   embedded: true>

Mongoid 7.2 raises Mongoid::Errors::InvalidQuery in this case.

$elemMatch

$elemMatch now supports specifying operators as top-level fields:

mixed_tasks_job = Job.new(tasks: [
  Task.new(name: 'Clean house', hours: 12, supplies: [{broom: 1}]),
  Task.new(name: 'Clean office', hours: [8, 16]),
])

mixed_tasks_job.tasks.where(hours: {'$elemMatch': {'$lt': 20}}).first
# Mongoid 7.2:
# => #<Task _id: 5ef8c7202c97a6465e8a4ff3, name: "Clean office", hours: [8, 16]>
# Mongoid 7.1: error

Implicit matching under $elemMatch has been fixed and now works:

mixed_tasks_job.tasks.where(supplies: {'$elemMatch': {broom: 1}}).first
# Mongoid 7.2:
# => #<Task _id: 5ef8c9162c97a6465e8a4ff6, name: "Clean house", hours: 12, supplies: [{:broom=>1}]>
# Mongoid 7.1:
# => nil

For compatibility with MongoDB server, $elemMatch requires a Hash argument. Use $eq or $regex to perform equality comparisons or regular expression matches, respectively:

mixed_tasks_job.tasks.where(hours: {'$elemMatch': 8}).first
# Mongoid 7.2: raises Mongoid::Errors::InvalidQuery
# Mongoid 7.1:
# => nil

mixed_tasks_job.tasks.where(hours: {'$elemMatch': {'$eq': 8}}).first
# Mongoid 7.2:
# => #<Task _id: 5ef8ca0b2c97a6465e8a4ff9, name: "Clean office", hours: [8, 16]>
# Mongoid 7.1: error

$and, $nor, $or and Empty Argument Arrays

$and, $nor and $or operators now raise an exception when given empty arrays as arguments. This only applies to raw MQL query expressions; the corresponding Mongoid query methods continue to permit being called without arguments. In previous versions of Mongoid, $and would match when given an empty array of conditions and $nor and $or would not match when given empty arrays of conditions.

job.tasks.where('$and': []).first
# Mongoid 7.2: raises Mongoid::Errors::InvalidQuery
# Mongoid 7.1:
# => #<Task _id: 5ef8dc3e2c97a645ec86bb33, name: "Clean house", pattern: /test/, hours: 12>

job.tasks.where('$nor': []).first
# Mongoid 7.2: raises Mongoid::Errors::InvalidQuery
# Mongoid 7.1:
# => nil

job.tasks.where('$or': []).first
# Mongoid 7.2: raises Mongoid::Errors::InvalidQuery
# Mongoid 7.1:
# => nil

job.tasks.and.first
# Mongoid 7.2 and 7.1:
# => #<Task _id: 5ef8dc3e2c97a645ec86bb33, name: "Clean house", pattern: /test/, hours: 12>

job.tasks.nor.first
# Mongoid 7.2 and 7.1:
# => #<Task _id: 5ef8dc3e2c97a645ec86bb33, name: "Clean house", pattern: /test/, hours: 12>

job.tasks.or.first
# Mongoid 7.2 and 7.1:
# => #<Task _id: 5ef8dc3e2c97a645ec86bb33, name: "Clean house", pattern: /test/, hours: 12>

count and estimated_count Methods

Minor change: the count method on model classes and Criteria objects is now using the count_documents driver helper. This makes count seamlessly work in transactions.

Model classes now also have the estimated_count method to obtain an approximate number of documents in the collection. This method is roughly equivalent to the count method in Mongoid 7.1 and earlier, except estimated_count does not accept filter conditions.

The new behavior is further described in the Additional Query Methods section.

any? on has_many Associations

Minor change: the any? method on has_many associations was optimized to only retrieve the _id field when querying the database, instead of loading the entire association.

StringifiedSymbol Field Type

New feature: the StringifiedSymbol field type was added for storing Ruby symbol values in MongoDB in a manner interoperable with other programming languages.

Changing the Discriminator Key

New feature: Mongoid now supports changing the default discriminator key from the default _type when using inheritance. This can be done by setting the discriminator_key field on the parent class or globally. To set the discriminator key on the parent class:

class Shape
  include Mongoid::Document

  self.discriminator_key = "shape_type"
end

class Circle < Shape
end

class Rectangle < Shape
end

To set the discrminator key globally:

Mongoid.discriminator_key = "global_discriminator"

class Shape
  include Mongoid::Document
end

class Circle < Shape
end

class Rectangle < Shape
end

Changing the Discriminator Value

New feature: Mongoid now also supports changing the discriminator value from the default value, which is the class name. The discriminator value can be changed by setting the discriminator_value on that class:

class Shape
  include Mongoid::Document
end

class Circle < Shape
  self.discriminator_value = "round thing"
end

class Rectangle < Shape
end

Shard Key Used For Reloading

Minor change: When sharding is used, Mongoid 7.2 expects the shard key declared in models to match the shard key in the database for the respective collections. In Mongoid 7.2 model reloading (either explicit via the reload method or implicit as part of persistence operations) uses the shard key, if one is defined, in the find command in addition to the id field value. This improves the performance of document reloading, and consequently some persistence operations, in sharded clusters, especially those with geographically distributed shards.

Consider a class Band whose documents are sharded by the name key.

class Band
  include Mongoid::Document
  field :name, type: String

  shard_key :name
end

Example Mongoid 7.2 behavior:

band = Band.create(name: "Destiny's Child")
band.reload
# Command logs: { "find"=>"bands", "filter"=>{ "_id"=>BSON::ObjectId('...') "name"=>"Destiny's Child" } }

Example Mongoid 7.1 behavior:

band = Band.create(name: "Destiny's Child")
band.reload
# Command logs: { "find"=>"bands", "filter"=>{"_id"=>BSON::ObjectId('...') } }

Mongoid provides sharding management Rake tasks to shard collections according to shard keys declared in models.

Query Cache Moved to Driver

Minor change: Ruby driver version 2.14.0 implements a new and improved query cache. When using driver version 2.14.0 or newer, Mongoid will use the driver’s query cache to cache query results.

The driver query cache introduces the following improvements:

  • Caching multi-batch query results
  • Taking a query’s read concern and read preference into account when deciding when to return cached results
  • Invalidating the cache after bulk write operations and aggregation operations with $out and $merge pipeline stages
  • Invalidating the cache after transaction commit and abort operations
  • Improved performance of queries with limits
  • Caching aggregation results
  • More efficient query cache invalidation

Mongoid’s query cache, which will now be referred to as the “legacy query cache,” has been deprecated. Mongoid will retain the legacy query cache for use with older versions of the driver.

The interface for enabling and disabling the query cache in Mongoid has not changed. When using driver versions 2.14.0 and newer, this interface will enable or disable the query cache in the driver.

The driver query cache is more correct and more effective than the legacy Mongoid query cache. If you plan to use the query cache, it is recommended that you upgrade to driver version 2.14.

To read more about the query cache improvements made in the driver, see the Ruby driver documentation.

To read more about using the query cache with Mongoid and the limitations of the legacy query cache, see the query cache documentation.

Upgrading to Mongoid 7.1

The following sections describe significant changes in Mongoid 7.1.

Condition Combination

Breaking change: In Mongoid 7.1, when condition methods are invoked on a Criteria object, they always add new conditions to the existing conditions in the Criteria object. Previously new conditions could have replaced existing conditions in some circumstances.

Example Mongoid 7.1 behavior:

Band.where(id: 1).where(id: 2)
# => #<Mongoid::Criteria
#   selector: {"_id"=>1, "$and"=>[{"_id"=>2}]}
#   options:  {}
#   class:    Band
#   embedded: false>

Corresponding Mongoid 7.0 behavior:

Band.where(id: 1).where(id: 2)
# => #<Mongoid::Criteria
#   selector: {"_id"=>2}
#   options:  {}
#   class:    Band
#   embedded: false>

Known issue: When using Ruby 2.6 and lower, when adding multiple conditions on the same field using the same operator, the operator must be given as a string, not as a symbol. The following invocations fail:

Band.and({year: {'$in': [2020]}}, {year: {'$in': [2020]}})
# Traceback (most recent call last):
#         2: from (irb):10
#         1: from (irb):10:in `rescue in irb_binding'
# NoMethodError (undefined method `start_with?' for :$in:Symbol)

Band.and(year: {'$in': [2020]}).and(year: {'$in': [2020]})
# Traceback (most recent call last):
#         2: from (irb):11
#         1: from (irb):11:in `rescue in irb_binding'
# NoMethodError (undefined method `start_with?' for :$in:Symbol)

Use string keys instead:

# Band.and({year: {'$in' => [2020]}}, {year: {'$in' => [2020]}})
# => #<Mongoid::Criteria
#   selector: {"year"=>{"$in"=>[2020]}, "$and"=>[{"year"=>{"$in"=>[2020]}}]}
#   options:  {}
#   class:    Band
#   embedded: false>

Band.and(year: {'$in' => [2020]}).and(year: {'$in' => [2020]})
# => #<Mongoid::Criteria
#   selector: {"year"=>{"$in"=>[2020]}, "$and"=>[{"year"=>{"$in"=>[2020]}}]}
#   options:  {}
#   class:    Band
#   embedded: false>

This issue is rectified in Mongoid 7.3.

Logical Operations

Breaking change: In Mongoid 7.1 logical operator methods or and nor treat the receiver as one of the operands. This behavior matches that of ActiveRecord. Previously, or and nor added their parameters as additional conditions to the receiver.

Example Mongoid 7.1 behavior:

Band.where(name: 'One').or(name: 'Two')
# => #<Mongoid::Criteria
#   selector: {"$or"=>[{"name"=>"One"}, {"name"=>"Two"}]}
#   options:  {}
#   class:    Band
#   embedded: false>

Corresponding Mongoid 7.0 behavior:

Band.where(name: 'One').or(name: 'Two')
# => #<Mongoid::Criteria
#   selector: {"name"=>"One", "$or"=>[{"name"=>"Two"}]}
#   options:  {}
#   class:    Band
#   embedded: false>

To add or or nor parameters to the existing query without disjuncting the existing query, create a separate scope:

Band.where(name: 'One').and(Band.or({name: 'Two'}, {name: 'Three'}))
# => #<Mongoid::Criteria
#   selector: {"name"=>"One", "$and"=>[{"$or"=>[{"name"=>"Two"}, {"name"=>"Three"}]}]}
#   options:  {}
#   class:    Band
#   embedded: false>

Alternatively, use any_of which behaves as or did in Mongoid 7.0:

Band.where(name: 'One').any_of({name: 'Two'}, {name: 'Three'})
# => #<Mongoid::Criteria
#   selector: {"name"=>"One", "$or"=>[{"name"=>"Two"}, {"name"=>"Three"}]}
#   options:  {}
#   class:    Band
#   embedded: false>

For more information, please review the logical operations section of the documentation.

Merge Strategies

Breaking change: In Mongoid 7.1, Merge strategies must be explicitly requested. Previously, all defaulted to the union strategy and in and nin defaulted to the intersect strategy. In Mongoid 7.1, there is no merge strategy applied by default.

Example Mongoid 7.1 behavior:

Band.all(a: 1).all(a: 2)
# => #<Mongoid::Criteria
#   selector: {"a"=>{"$all"=>[1]}, "$and"=>[{"a"=>{"$all"=>[2]}}]}
#   options:  {}
#   class:    Band
#   embedded: false>

Default Mongoid 7.0 behavior:

Band.all(a: 1).all(a: 2)
# => #<Mongoid::Criteria
#   selector: {"a"=>{"$all"=>[1, 2]}}
#   options:  {}
#   class:    Band
#   embedded: false>

To achieve the same behavior in Mongoid 7.1, the desired merge strategy must be explicitly requested:

Band.all(a: 1).union.all(a: 2)
# => #<Mongoid::Criteria
#   selector: {"a"=>{"$all"=>[1, 2]}}
#   options:  {}
#   class:    Band
#   embedded: false>

Required Condition Arguments

Breaking change: nil arguments to Criteria methods are no longer accepted. For example, the following invocation is now an error:

Band.where(nil)

Breaking change: Most Criteria methods (other than logical operations) can no longer be called without arguments. For example, the following invocation is now an error:

Band.in

and, or, nor, not, any_of and where can be called without arguments.

Generated Queries

Minor change: Mongoid 7.1 will simplify the Criteria selectors where possible by eliding unnecessary logical operators, typically $and.

Example Mongoid 7.1 behavior:

Band.where(year: 2000).and(name: 'One')
# => #<Mongoid::Criteria
#   selector: {"year"=>2000, "name"=>"One"}
#   options:  {}
#   class:    Band
#   embedded: false>

Corresponding Mongoid 7.0 behavior:

Band.where(year: 2000).and(name: 'One')
# => #<Mongoid::Criteria
#   selector: {"year"=>2000, "$and"=>[{"name"=>"One"}]}
#   options:  {}
#   class:    Band
#   embedded: false>

Mongoid 7.1 also takes steps to produce the same selector shapes when semantically the same query is constructed using different code paths. For example, the following two approaches result in the same generated selector:

Band.where(name: /ne/).and(name: 'One')

Band.and({name: /ne/}, {name: 'One'})

# => #<Mongoid::Criteria
#   selector: {"name"=>/ne/, "$and"=>[{"name"=>"One"}]}
#   options:  {}
#   class:    Band
#   embedded: false>

Ruby Version Support

As of version 7.1, Mongoid supports Ruby 2.3+ and JRuby 9.2. Support for Ruby 2.2 and JRuby 9.1 has been dropped.

Changes in Mongoid 7.1.0

Improvement: Multiple atomic operations can now be grouped to be executed as a single atomic operation.

Improvement: Shard key declarations now support hashed shard keys, and a Rake task to shard collection has been added to the sharding management Rake tasks.

Changes in Mongoid 7.1.1

Breaking change: The behavior of any_of was reverted to its Mongoid 7.0 behavior. As was the case in Mongoid 7.0, any_of now does not treat the receiver as one of its operands:

Band.where(year: 1990).any_of({name: 'One'}, {name: 'Two'})
# => #<Mongoid::Criteria
#   selector: {"year"=>1990, "$or"=>[{"name"=>"One"}, {"name"=>"Two"}]}
#   options:  {}
#   class:    Band
#   embedded: false>

or behaves the same way in Mongoid 7.1.1 and 7.1.0:

Band.where(year: 1990).or({name: 'One'}, {name: 'Two'})
# => #<Mongoid::Criteria
#   selector: {"$or"=>[{"year"=>1990}, {"name"=>"One"}, {"name"=>"Two"}]}
#   options:  {}
#   class:    Band
#   embedded: false>

Minor change: The value of include_root_in_json is now properly respected throughout the model hierarchy. Previously the values set on model classes were ignored.

Improvement: Model.where can now be called with no arguments.

Improvement: Polymorphic belongs_to associations can now be eagerly loaded.

Upgrading to Mongoid 7.0

Significant improvements and changes in Mongoid 7.0 are listed below. Please note that improvements that have been backported to Mongoid 6.x are not included in this list.

The behavior of read-only attributes now matches that of ActiveRecord.

Referenced associations now support all dependent behaviors that ActiveRecord supports.

$unwind operator support added to the aggregation pipeline builder DSL.

background_indexing Mongoid configuration option added.

Mongoid 7.0 requires MongoDB server 2.6 or newer, Ruby 2.2.2 or higher and supports Rails 5.1-6.0.

New in version 7.0.3: Embedded matchers now support the $eq operator.

New in version 7.0.5: Mongoid now officially supports Rails 6.0.

Upgrading to Mongoid 6

Mongoid 6 is compatible with Rails 5 and requires Ruby 2.2.2 or higher.

Please see the list of behavior changes for version 6.0.0.rc0 and version 6.0.0.beta for specific changes that might be required in your code.

Upgrading to Mongoid 5

The underlying driver has changed from Moped to the official MongoDB Ruby driver. For all users dropping down to the driver level for operations, please see the driver documentation for help with that API.

All Mongoid configuration options have changed, as the underlying driver has changed from Moped to the official MongoDB Ruby driver. Please see the configuration section for a detailed description of all the new options.

All references to session are now replaced with client. This includes the mongoid.yml configuration, store_in options, and all exceptions and modules with Session in the name.

find_and_modify has been removed and replaced with 3 options: find_one_and_update, find_one_and_delete and find_one_and_replace.

text_search has been removed as it is now a $text option in a query from MongoDB 2.6 onward.

first and last no longer add an _id sort when no sorting options have been provided. In order to guarantee that a document is the first or last, it needs to now contain an explicit sort.

Mongoid 5 supports MongoDB 2.4 and higher only - MongoDB 2.2 is no longer supported.