Mongoid 9.0

This page describes significant changes and improvements in Mongoid 9.0. 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.

Railsmdb for Mongoid

To coincide with the release of Mongoid 9.0 a new command-line utility for creating, updating, managing, and maintaining Rails applications has also been made generally available!

railsmdb makes it easier to work with MongoDB from the command line through common generators that Ruby on Rails developers are already familiar with.

For example, you can use railsmdb to generate stubs for new Mongoid models:

$ bin/railsmdb generate model person

This will create a new model at app/models/person.rb:

class Person
  include Mongoid::Document
  include Mongoid::Timestamp

You can specify the fields of the model as well:

# bin/railsmdb generate model person name:string birth:date

class Person
  include Mongoid::Document
  include Mongoid::Timestamp
  field :name, type: String
  field :birth, type: Date

You can instruct the generator to make the new model a subclass of another, by passing the --parent option:

# bin/railsmdb generate model student --parent=person

class Student < Person
  include Mongoid::Timestamp

And if you need to store your models in a different collection than can be inferred from the model name, you can specify --collection:

# bin/railsmdb generate model course --collection=classes

class Course
  include Mongoid::Document
  include Mongoid::Timestamp
  store_in collection: 'classes'

For more information see the GitHub Repository.

Support for Ruby 2.6 and JRuby 9.3 Dropped

Mongoid 9 requires Ruby 2.7 or newer or JRuby 9.4. Earlier Ruby and JRuby versions are not supported.

Support for Rails 5 Dropped

Mongoid 9 requires Rails 6.0 or newer. Earlier Rails versions are not supported.

Deprecated class Mongoid::Errors::InvalidStorageParent removed

The deprecated class Mongoid::Errors::InvalidStorageParent has been removed.

href="">around* callbacks for embedded documents are now ignored

Mongoid 8.x and older allows user to define href="">around* callbacks for embedded documents. Starting from 9.0 these callbacks are ignored and will not be executed. A warning will be printed to the console if such callbacks are defined.

If you want to restore the old behavior, you can set Mongoid.around_embedded_document_callbacks to true in your application.


Enabling href="">around* callbacks for embedded documents is not recommended as it may cause SystemStackError exceptions when a document has many embedded documents. See MONGOID-5658 for more details.

for_js method is deprecated

The for_js method is deprecated and will be removed in Mongoid 10.0.

Deprecated options removed

Breaking change: The following config options are removed in Mongoid 9.0. Please ensure you have removed all references to these from your app. If you were using config.load_defaults 8.1 prior to upgrading, you will not experience any behavior change. Refer to earlier release notes for the meaning of each option.

In addition, support for config.load_defaults versions 7.5 and prior has been dropped (you must use a minimum of version 8.0.)

Deprecated functionality removed

Breaking change: The following deprecated functionality is now removed:

touch method now clears changed state

In Mongoid 8.x and older touch method leaves models in the changed state:

# Mongoid 8.x behaviour
band = Band.create!
band.changed? # => true
band.changes # => {"updated_at"=>[2023-01-30 13:12:57.477191135 UTC, 2023-01-30 13:13:11.482975646 UTC]}

Starting from 9.0 Mongoid now correctly clears changed state after using touch method.

# Mongoid 9.0 behaviour
band = Band.create!
band.changed? # => false
band.changes # => {}

Sandbox Mode for Rails Console

Mongoid now supports Rails console sandbox mode. If the Rails console started with --sandbox flag, Mongoid starts a transaction on the :default client before opening the console. This transaction won't be committed; therefore, all the commands executed in the console using the :default client won't be persisted in the database.


If you execute commands in the sandbox mode using any other client than default, these changes will be persisted as usual.

New Transactions API

Mongoid 9.0 introduces new transactions API that is inspired by ActiveRecord:

Band.transaction do
  Band.create(title: 'Led Zeppelin')

band = Band.create(title: 'Deep Purple')
band.transaction do
  band.active = false

Please consult transactions documentation <transactions> for more details.

Embedded Documents Always Use Parent Persistence Context

Mongoid 8.x and older allows user to specify persistence context for an embedded document (using store_in macro). In Mongoid 9.0 these settings are ignored for embedded documents; an embedded document now always uses the persistence context of its parent.

Support for Passing Raw Values into Queries

When performing queries, it is now possible skip Mongoid's type coercion logic using the ::Mongoid::RawValue wrapper class. This can be useful when legacy data in the database is of a different type than the field definition.

class Person
  include Mongoid::Document
  field :age, type: Integer

# Query for the string "42", not the integer 42
Person.where(age: Mongoid::RawValue("42"))

Raise AttributeNotLoaded error when accessing fields omitted from query projection

When attempting to access a field on a model instance which was excluded with the .only or .without query projections methods when the instance was loaded, Mongoid will now raise a ::Mongoid::Errors::AttributeNotLoaded error.

#=> raises Mongoid::Errors::AttributeNotLoaded

Band.without(:label).first.label = 'Sub Pop Records'
#=> raises Mongoid::Errors::AttributeNotLoaded

In earlier Mongoid versions, the same conditions would raise an ActiveModel::MissingAttributeError. Please check your code for any Mongoid-specific usages of this class, and change them to ::Mongoid::Errors::AttributeNotLoaded. Note additionally that AttributeNotLoaded inherits from ::Mongoid::Errors::MongoidError, while ActiveModel::MissingAttributeError does not.

Use configured time zone to typecast Date to Time in queries

When querying for a Time field using a Date value, Mongoid now correctly considers Time.zone to perform type conversion.

class Magazine
  include Mongoid::Document

  field :published_at, type: Time

Time.zone = 'Asia/Tokyo'

Magazine.gte(published_at: Date.parse('2022-09-26'))
#=> will return all results on or after Sept 26th, 2022
#   at 0:00 in Asia/Tokyo time zone.

In prior Mongoid versions, the above code would ignore the Time.zone (irrespective of the now-removed :use_activesupport_time_zone setting) and always using the system time zone to perform the type conversion.

Note that in prior Mongoid versions, typecasting Date to Time during persistence operations was already correctly using time zone.

#touch method on embedded documents correctly handles touch: false` option

When the touch: false option is set on an embedded_in relation, calling the #touch method on an embedded child document will not invoke #touch on its parent document.

class Address
  include Mongoid::Document
  include Mongoid::Timestamps

  embedded_in :mall, touch: false

class Mall
  include Mongoid::Document
  include Mongoid::Timestamps

  embeds_many :addresses

mall = Mall.create!
address = mall.addresses.create!

#=> updates address.updated_at but not mall.updated_at

In addition, the #touch method has been optimized to perform one persistence operation per parent document, even when using multiple levels of nested embedded documents.

embedded_in associations now default to touch: true

Updating an embedded subdocument will now automatically touch the parent, unless you explicitly set touch: false on the relation:

class Address
  include Mongoid::Document
  include Mongoid::Timestamps

  embedded_in :mall, touch: false

For all other associations, the default remains touch: false.

Flipped default for :replace option in #upsert

Mongoid 8.1 added the :replace option to the #upsert method. This option was used to specify whether or not the existing document should be updated or replaced.

Mongoid 9.0 flips the default of this flag from true => false.

This means that, by default, Mongoid 9 will update the existing document and will not replace it.

The immutability of the _id field is now enforced

Prior to Mongoid 9.0, mutating the _id field behaved inconsistently depending on whether the document was top-level or embedded, and depending on how the update was performed. As of 9.0, changing the _id field will now raise an exception when the document is saved, if the document had been previously persisted.

Mongoid 9.0 also introduces a new feature flag, immutable_ids, which defaults to true.

Mongoid::Config.immutable_ids = true

When set to false, the older, inconsistent behavior is restored.

Support Field Aliases on Index Options

Support has been added to use aliased field names in the following options of the index macro: partial_filter_expression, weights, wildcard_projection.

class Person
  include Mongoid::Document
  field :a, as: :age
  index({ age: 1 }, { partial_filter_expression: { age: { '$gte' => 20 } })

The expansion of field name aliases in index options such as partial_filter_expression is performed according to the behavior of MongoDB server 6.0. Future server versions may change how they interpret these options, and Mongoid's functionality may not support such changes.

BSON 5 and BSON::Decimal128 Fields

When BSON 4 or earlier is present, any field declared as BSON::Decimal128 will return a BSON::Decimal128 value. When BSON 5 is present, however, any field declared as BSON::Decimal128 will return a BigDecimal value by default.

class Model
  include Mongoid::Document

  field :decimal_field, type: BSON::Decimal128

# under BSON <= 4
Model.first.decimal_field.class #=> BSON::Decimal128

# under BSON >= 5
Model.first.decimal_field.class #=> BigDecimal

If you need literal BSON::Decimal128 values with BSON 5, you may instruct Mongoid to allow literal BSON::Decimal128 fields:

Model.first.decimal_field.class #=> BigDecimal

Mongoid.allow_bson5_decimal128 = true
Model.first.decimal_field.class #=> BSON::Decimal128

The allow_bson5_decimal128 option only has any effect under BSON 5 and later. BSON 4 and earlier ignore the setting entirely.

Search Index Management with MongoDB Atlas

When connected to MongoDB Atlas, Mongoid now supports creating and removing search indexes. You may do so programmatically, via the Mongoid::SearchIndexable API:

class SearchablePerson
  include Mongoid::Document

  search_index { ... } # define the search index here

# create the declared search indexes; this returns immediately, but the
# search indexes may take several minutes before they are available.

# query the available search indexes
SearchablePerson.search_indexes.each do |index|
  # ...

# remove all search indexes from the model's collection

If you are not connected to MongoDB Atlas, the search index definitions are ignored. Trying to create, enumerate, or remove search indexes will result in an error.

There are also rake tasks available, for convenience:

# create search indexes for all models; waits for indexes to be created
# and shows progress on the terminal.
$ rake mongoid:db:create_search_indexes

# as above, but returns immediately and lets the indexes be created in the
# background
$ rake WAIT_FOR_SEARCH_INDEXES=0 mongoid:db:create_search_indexes

# removes search indexes from all models
$ rake mongoid:db:remove_search_indexes

Time.configured has been removed

Time.configured returned either the time object wrapping the configured time zone, or the standard Ruby Time class. This allowed you to query a time value even if no time zone had been configured.

Mongoid now requires that you set a time zone if you intend to do anything with time values (including using timestamps in your documents). Any uses of Time.configured must be replaced with Time.zone.

# before:
puts Time.configured.now

# after:
puts Time.zone.now

# or, better for finding the current Time specifically:
puts Time.current

If you do not set a time zone, you will see errors in your code related to nil values. If you are using Rails, the default time zone is already set to UTC. If you are not using Rails, you may set a time zone at the start of your program like this:

Time.zone = 'UTC'

This will set the time zone to UTC. You can see all available time zone names by running the following command:

$ ruby -ractive_support/values/time_zone \
  -e 'puts ActiveSupport::TimeZone::MAPPING.keys'

Records now remember the persistence context in which they were loaded/created

Consider the following code:

record = Model.with(collection: 'other_collection') { Model.first }
record.update(field: 'value')

Prior to Mongoid 9.0, this could would silently fail to execute the update, because the storage options (here, the specification of an alternate collection for the model) would not be remembered by the record. Thus, the record would be loaded from "other_collection", but when updated, would attempt to look for and update the document in the default collection for Model. To make this work, you would have had to specify the collection explicitly for every update.

As of Mongoid 9.0, records that are created or loaded under explicit storage options, will remember those options (including a named client, a different database, or a different collection).

If you need the legacy (pre-9.0) behavior, you can enable it with the following flag:

Mongoid.legacy_persistence_context_behavior = true

This flag defaults to false in Mongoid 9.

Bug Fixes and Improvements

This section will be for smaller bug fixes and improvements: