Mongoid 8.0
On this page
- Support for MongoDB 3.4 and Earlier Servers Dropped
- Support for Ruby 2.5 Dropped
- Support for Rails 5.1 Dropped
- Default Option Values Changed
Decimal128
-backedBigDecimal
Fields- Storing/Retrieving/Evolving Uncastable Values
- Changes to the
attributes_before_type_cast
Hash - Order of Callback Invocation
Changeable
Module Behavior Made Compatible WithActiveModel::Dirty
*_previously_was
,previously_new_record?
, andpreviously_persisted?
helpers- Unknown Field Type Symbols/Strings Prohibited
any_of
Adds Multiple Arguments As Top-Level Conditions#pluck
on Embedded Criteria Returnsnil
Values- Replaced
Mongoid::Criteria#geo_spacial
with#geo_spatial
- Implemented
.tally
method onMongoid#Criteria
- Implemented
.pick
method onMongoid#Criteria
find
delegates toEnumerable#find
when given a block- No Longer Persisting Documents with Invalid
belongs_to
Associations - Update Local HABTM Association Correctly
- Repaired Storing Strings in BSON::Binary fields
- Removed
Document#to_a
Method - Removed
:drop_dups
Option from Indexes - Removed
Mongoid::Errors::EagerLoad
Exception Class - Removed Deprecated Constants
- Removed
Array#update_values
andHash#update_values
methods - Deprecated the
geoHaystack
,geoSearch
Options :id_sort
Option on#first/last
Removed- Mongoid::Criteria Cache Removed
This page describes significant changes and improvements in Mongoid 8.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.
Support for MongoDB 3.4 and Earlier Servers Dropped
Mongoid 8 requires MongoDB 3.6 or newer. Earlier server versions are not supported.
Support for Ruby 2.5 Dropped
Mongoid 8 requires Ruby 2.6 or newer. Earlier Ruby versions are not supported.
Support for Rails 5.1 Dropped
Mongoid 8 requires Rails 5.2 or newer. Earlier Rails versions are not supported.
Default Option Values Changed
Breaking change: The following options have had their default values changed in Mongoid 8.0:
:broken_aggregables
=>false
:broken_alias_handling
=>false
:broken_and
=>false
:broken_scoping
=>false
:broken_updates
=>false
:compare_time_by_ms
=>true
:legacy_attributes
=>false
:legacy_pluck_distinct
=>false
:legacy_triple_equals
=>false
:object_id_as_json_oid
=>false
:overwrite_chained_operators
=>false
Please refer to configuration option for the description and effects of each of these options.
Decimal128
-backed BigDecimal
Fields
Mongoid 8 introduces the map_big_decimal_to_decimal128
feature flag, which
allows values assigned to a field of type BigDecimal
to be stored as type
String
in the database for compatibility with Mongoid 7 and earlier. In
Mongoid 8 by default (with this feature flag turned on), values assigned to
fields of type BigDecimal
are stored in the database as type
BSON::Decimal128
. In Mongoid 7 and earlier, and in Mongoid 8 with this
feature flag turned off, values assigned to fields of type BigDecimal
are
stored as Strings. See the section on BigDecimal Fields
for more details.
Storing/Retrieving/Evolving Uncastable Values
Breaking change: In Mongoid 8, Mongoid standardizes the storing, retrieving
and evolving of "uncastable values." On attempting to read or write an
uncastable value, a nil
is returned or written instead. When attempting to
evolve an uncastable value, the inputted value is returned. See the section on
Uncastable Values for more details.
Some mongoize
, demongoize
and evolve
methods were also changed to
perform consistently with rails and the other mongoize
, demongoize
and
evolve
methods. The following is a table of the changes in functionality:
+--------------+------------------------+------------------------+-----------------------+
| Field Type | Situation | Previous Functionality | New Functionality |
+==============+========================+========================+=======================+
| Boolean | When a non-boolean | return false
| return nil
|
| | string is assigned: | | |
| | "bogus value" | | |
+--------------+------------------------+------------------------+-----------------------+
| Array/Hash | When a value that is | raise InvalidValue
| return nil
|
| | not an array or hash | error | |
| | is assigned | | |
+--------------+------------------------+------------------------+-----------------------+
| Set | When a value that is | raise NoMethodError``| return ``nil
|
| | not a set is assigned: | Exception: undefined | |
| | 1 | method to_a
for | |
| | | 1:Integer | |
+--------------+------------------------+------------------------+-----------------------+
| Regexp | When persisting and | return a | return a |
| | reading a Regexp from | BSON::Regexp::Raw
| Regexp
|
| | the database | | |
+--------------+------------------------+------------------------+-----------------------+
| Time/DateTime| When assigning a | raise NoMethodError``| return ``nil
|
| | bogus value: :bogus``| Exception: undefined | |
| | | method ``to_i
| |
| | | for :bogus:Symbol | |
+--------------+------------------------+------------------------+-----------------------+
| Time/DateTime| When demongoizing a | raise NoMethodError``| "bogus": |
| | non-Time value: | Exception: undefined | return ``nil
|
| | "bogus", | method getlocal
| |
| | Date.today
| for "bogus":String | Date.today
: |
| | | | return a |
| | | | Time/DateTime
|
+--------------+------------------------+------------------------+-----------------------+
| Date | When assigning or | raise NoMethodError``| return ``nil
|
| | demongoizing a bogus | Exception: undefined | |
| | value: :bogus | method year
| |
| | | for :bogus:Symbol | |
+--------------+------------------------+------------------------+-----------------------+
| Time/DateTime| When demongoizing a | raise NoMethodError``| return a |
| /Date | valid string: | Exception: undefined | ``Time/DateTime/Date``|
| | "2022-07-14 14:45:51 | method ``getlocal
| |
| | -0400" | for "2022-07-14 | |
| | | 14:45:51 -0400":String | |
+--------------+------------------------+------------------------+-----------------------+
| All Types | When an uncastable | undefined behavior, | return nil
|
| | value is assigned or | occasionally raise | |
| | demongoized | NoMethodError
| |
+--------------+------------------------+------------------------+-----------------------+
| All Types | When an uncastable | undefined behavior, | return inputted value |
| | value is evolved | occasionally raise | |
| | | NoMethodError
| |
+--------------+------------------------+------------------------+-----------------------+
Note
The demongoize
methods on container objects (i.e. Hash, Array) have not
changed to prevent bugs when modifying and saving those objects. See
https://jira.mongodb.org/browse/MONGOID-2951 for a longer discussion on these
bugs.
Changes to the attributes_before_type_cast
Hash
The attributes_before_type_cast
hash has been changed to function more like
ActiveRecord:
On instantiation of a new model (without parameters), the
attributes_before_type_cast
hash has the same contents as theattributes
hash. If parameters are passed to the initializer, those values will be stored in theattributes_before_type_cast
hash before they aremongoized
.When assigning a value to the model, the
mongoized
value (i.e. when assiging '1' to an Integer field, it ismongoized
to 1) is stored in theattributes
hash, whereas the raw value (i.e. '1') is stored in theattributes_before_type_cast
hash.When saving, creating (i.e. using the
create!
method), or reloading the model, theattributes_before_type_cast
hash is reset to have the same contents as theattributes
hash.When reading a document from the database, the
attributes_before_type_cast
hash contains the attributes as they appear in the database, as opposed to theirdemongoized
form.
Order of Callback Invocation
Breaking change: Mongoid 8.0 changes the order of _create and _save callback invocation for documents with associations.
Referenced associations (has_one
and has_many
):
+---------------------------------------+---------------------------------------+ | Mongoid 8.0 | Mongoid 7 | +=======================================+=======================================+ | Parent :before_save | Parent :before_save | +---------------------------------------+---------------------------------------+ | Parent :around_save_open | Parent :around_save_open | +---------------------------------------+---------------------------------------+ | Parent :before_create | Parent :before_create | +---------------------------------------+---------------------------------------+ | Parent :around_create_open | Parent :around_create_open | +---------------------------------------+---------------------------------------+ | Parent persisted in MongoDB | Parent persisted in MongoDB | +---------------------------------------+---------------------------------------+ | Child :before_save | Parent :around_create_close | +---------------------------------------+---------------------------------------+ | Child :around_save_open | Parent :after_create | +---------------------------------------+---------------------------------------+ | Child :before_create | Child :before_save | +---------------------------------------+---------------------------------------+ | Child :around_create_open | Child :around_save_open | +---------------------------------------+---------------------------------------+ | | Child :before_create | +---------------------------------------+---------------------------------------+ | | Child :around_create_open | +---------------------------------------+---------------------------------------+ | Child persisted in MongoDB | Child persisted in MongoDB | +---------------------------------------+---------------------------------------+ | Child :around_create_close | Child :around_create_close | +---------------------------------------+---------------------------------------+ | Child :after_create | Child :after_create | +---------------------------------------+---------------------------------------+ | Child :around_save_close | Child :around_save_close | +---------------------------------------+---------------------------------------+ | Child :after_save | Child :after_save | +---------------------------------------+---------------------------------------+ | Parent :around_create_close | Parent :around_save_close | +---------------------------------------+---------------------------------------+ | Parent :after_create | Parent :after_save | +---------------------------------------+---------------------------------------+ | Parent :around_save_close | | +---------------------------------------+---------------------------------------+ | Parent :after_save | | +---------------------------------------+---------------------------------------+
Embedded associations (embeds_one
and embeds_many
):
+---------------------------------------+---------------------------------------+ | Mongoid 8.0 | Mongoid 7 | +=======================================+=======================================+ | Parent :before_save | Child :before_save | +---------------------------------------+---------------------------------------+ | Parent :around_save_open | Child :around_save_open | +---------------------------------------+---------------------------------------+ | Parent :before_create | Child :around_save_close | +---------------------------------------+---------------------------------------+ | Parent :around_create_open | Child :after_save | +---------------------------------------+---------------------------------------+ | Child :before_save | Parent :before_save | +---------------------------------------+---------------------------------------+ | Child :around_save_open | Parent :around_save_open | +---------------------------------------+---------------------------------------+ | Child :before_create | Child :before_create | +---------------------------------------+---------------------------------------+ | Child :around_create_open | Child :around_create_open | +---------------------------------------+---------------------------------------+ | | Child :around_create_close | +---------------------------------------+---------------------------------------+ | | Child :after_create | +---------------------------------------+---------------------------------------+ | | Parent :before_create | +---------------------------------------+---------------------------------------+ | | Parent :around_create_open | +---------------------------------------+---------------------------------------+ | Document persisted in MongoDB | Document persisted in MongoDB | +---------------------------------------+---------------------------------------+ | Child :around_create_close | | +---------------------------------------+---------------------------------------+ | Child :after_create | | +---------------------------------------+---------------------------------------+ | Child :around_save_close | | +---------------------------------------+---------------------------------------+ | Child :after_save | | +---------------------------------------+---------------------------------------+ | Parent :around_create_close | Parent :around_create_close | +---------------------------------------+---------------------------------------+ | Parent :after_create | Parent :after_create | +---------------------------------------+---------------------------------------+ | Parent :around_save_close | Parent :around_save_close | +---------------------------------------+---------------------------------------+ | Parent :after_save | Parent :after_save | +---------------------------------------+---------------------------------------+
Changeable
Module Behavior Made Compatible With ActiveModel::Dirty

When updating documents, it is now possible to get updated attribute values
in after_*
callbacks. This follows ActiveRecord/ActiveModel behavior.
class Cat include Mongoid::Document field :age, type: Integer after_save do p self p attribute_was(:age) end end a = Cat.create! a.age = 2 a.save!
Mongoid 8.0 output:
#<Cat _id: 60aef1652c97a617438dc9bb, age: 2> 2
Mongoid 7 output:
#<Cat _id: 60aef1652c97a617438dc9bb, age: 2> nil
Notice that in 7 attribute_was(:age)
returns the old attribute value,
while in 8.0 attribute_was(:age)
returns the new value.
*_previously_was
, previously_new_record?
, and previously_persisted?
helpers
Mongoid 8.0 introduces ActiveModel-compatible *_previously_was
helpers,
as well as ActiveRecord-compatible previously_new_record?
and
previously_persisted?
helpers:
class User include Mongoid::Document field :name, type: String field :age, type: Integer end user = User.create!(name: 'Sam', age: 18) user.previously_new_record? # => true user.name = "Nick" user.save! user.name_previously_was # => "Sam" user.age_previously_was # => 18 user.previously_new_record? # => false user.destroy user.previously_persisted? # => true
Unknown Field Type Symbols/Strings Prohibited
Breaking change: Mongoid 8 prohibits using symbols and strings as field
types when these symbols and strings do not map to a known type. Previously
such usage would create a field of type Object
.
Mongoid 8 behavior:
class User include Mongoid::Document field :name, type: :bogus # => raises Mongoid::Errors::InvalidFieldType end
Mongoid 7 behavior:
class User include Mongoid::Document field :name, type: :bogus # Equivalent to: field :name end
any_of
Adds Multiple Arguments As Top-Level Conditions
Breaking change: When any_of
is invoked with multiple conditions, the
conditions are now added to the top level of the criteria, same as when
any_of
is invoked with a single condition. Previously when multiple
conditions were provided, and the criteria already had an $or
operator,
the new conditions would be added to the existing $or
as an additional
branch.
Mongoid 8.0 behavior:
Band.any_of({name: 'The Rolling Stones'}, {founded: 1990}). any_of({members: 2}, {last_tour: 1995}) # => # #<Mongoid::Criteria # selector: {"$or"=>[{"name"=>"The Rolling Stones"}, {"founded"=>1990}], # "$and"=>[{"$or"=>[{"members"=>2}, {"last_tour"=>1995}]}]} # options: {} # class: Band # embedded: false> Band.any_of({name: 'The Rolling Stones'}, {founded: 1990}).any_of({members: 2}) # => # #<Mongoid::Criteria # selector: {"$or"=>[{"name"=>"The Rolling Stones"}, {"founded"=>1990}], "members"=>2} # options: {} # class: Band # embedded: false>
Mongoid 7 behavior:
Band.any_of({name: 'The Rolling Stones'}, {founded: 1990}). any_of({members: 2}, {last_tour: 1995}) # => # #<Mongoid::Criteria # selector: {"$or"=>[{"name"=>"The Rolling Stones"}, {"founded"=>1990}, # {"members"=>2}, {"last_tour"=>1995}]} # options: {} # class: Band # embedded: false> Band.any_of({name: 'The Rolling Stones'}, {founded: 1990}).any_of({members: 2}) # => # #<Mongoid::Criteria # selector: {"$or"=>[{"name"=>"The Rolling Stones"}, {"founded"=>1990}], "members"=>2} # options: {} # class: Band # embedded: false>
#pluck
on Embedded Criteria Returns nil
Values
Mongoid 8 fixes a bug where calling #pluck
on a Mongoid::Criteria
for embedded documents discarded nil values. This behavior was
inconsistent with both the #pluck
method in ActiveSupport and
with how #pluck
works when reading documents from the database.
Mongoid 8.0 behavior:
class Address include Mongoid::Document embedded_in :mall field :street, type: String end class Mall include Mongoid::Document embeds_many :addresses end mall = Mall.create! mall.addresses.create!(street: "Elm Street") mall.addresses.create!(street: nil) # Pluck from embedded document criteria mall.addresses.all.pluck(:street) #=> ['Elm Street', nil]
Mongoid 7 behavior, given the same setup:
# Pluck from embedded document criteria mall.addresses.all.pluck(:street) #=> ['Elm Street']
For clarity, the following behavior is unchanged from Mongoid 7 to Mongoid 8.0:
# Pluck from database Mall.all.pluck('addresses.street') #=> [ ['Elm Street', nil] ] # Pluck using ActiveSupport Array#pluck mall.addresses.pluck(:street) #=> ['Elm Street', nil]
Replaced Mongoid::Criteria#geo_spacial
with #geo_spatial

The previously deprecated Mongoid::Criteria#geo_spacial
method has been
removed in Mongoid 8. It has been replaced one-for-one with #geo_spatial
which was added in Mongoid 7.2.0.
Implemented .tally
method on Mongoid#Criteria

Mongoid 8 implements the .tally
method on Mongoid#Criteria
. tally
takes a field name as a parameter and returns a mapping from values to their
counts. For example, take the following model:
class User include Mongoid::Document field :age end
and the following documents in the database:
{ _id: 1, age: 21 } { _id: 2, age: 21 } { _id: 3, age: 22 }
Calling tally
on the age field yields the following:
User.tally("age") # => { 21 => 2, 22 => 1 }
The tally
method accepts the dot notation and field aliases. It also
allows for tallying localized fields.
Implemented .pick
method on Mongoid#Criteria

Mongoid 8 implements the .pick
method on Mongoid#Criteria
. pick
takes one or more field names as a parameter and returns the values for those
fields from one document. Consider the following model:
class User include Mongoid::Document field :age end
and the following documents in the database:
{ _id: 1, age: 21 } { _id: 2, age: 21 } { _id: 3, age: 22 }
Calling pick
on the age field yields the following:
User.pick(:age) # => 21
This method does not apply a sort to the documents, so it will not necessarily return the values from the first document.
The pick
method accepts the dot notation and field aliases. It also
allows for picking localized fields.
find
delegates to Enumerable#find
when given a block
When given a block, without _id
arguments, find
delegates to
Enumerable#find
. Consider the following model:
class Band include Mongoid::Document field :name, type: String end Band.create!(name: "Depeche Mode") Band.create!(name: "The Rolling Stones")
Calling find
with a block returns the first document for which the block
returns true
:
Band.find do |b| b.name == "Depeche Mode" end # => #<Band _id: 62c58e383282a4cbe82bd74b, name: "Depeche Mode">
No Longer Persisting Documents with Invalid belongs_to
Associations
In Mongoid 8, if an invalid document is assigned to a belongs_to
association,
and the base document is saved, if the belongs_to
association had the
option optional: false
or optional
is unset and the global flag
belongs_to_required_by_default
is true, neither the document nor the
associated document will be persisted. For example, given the following
models:
class Parent include Mongoid::Document has_one :child field :name validates :name, presence: true end class Child include Mongoid::Document belongs_to :parent, autosave: true, validate: false, optional: false end child = Child.new parent = Parent.new child.parent = parent # parent is invalid, it does not have a name child.save
In this case, both the child and the parent will not be persisted.
Note
If save! were called, a validation error would be raised.
If optional is false, then the Child will be persisted with a parent_id, but the parent will not be persisted:
child = Child.new parent = Parent.new child.parent = parent # parent is invalid, it does not have a name child.save p Child.first # => <Child _id: 629a50b0d1327aad89d214d2, parent_id: BSON::ObjectId('629a50b0d1327aad89d214d3')> p Parent.first # => nil
If you want the functionality of neither document being persisted in Mongoid 7 or 8
and earlier, the validate: true
option can be set on the association. If
you want the functionality of only the Child being persisted, the validate:
false
option can be set on the association.
Update Local HABTM Association Correctly
In Mongoid 8, when pushing persisted elements to a HABTM association, the association will now update correctly without requiring a reload. For example:
class User include Mongoid::Document has_and_belongs_to_many :posts end class Post include Mongoid::Document has_and_belongs_to_many :users end user1 = User.create! user2 = User.create! post = user1.posts.create! p post.users.length # => 1 post.users << user2 p post.users.length # => 1 in Mongoid 7, 2 in Mongoid 8 p post.reload.users.length # => 2
As you can see from this example, after pushing user2
to the users array,
Mongoid 8 correctly updates the number of elements without requiring a reload.
Repaired Storing Strings in BSON::Binary fields
Breaking change: In Mongoid 8, storing a String in a field of type
BSON::Binary
will be stored in and returned from the database as a
BSON::Binary
. Prior to Mongoid 8 it was stored and returned as a String:
class Registry include Mongoid::Document field :data, type: BSON::Binary end registry = Registry.create!(data: "data!") p registry.data # => Mongoid 7: "data!" # => Mongoid 8: <BSON::Binary:0x2580 type=generic data=0x6461746121...> registry = Registry.find(registry.id) p registry.data # => Mongoid 7: "data!" # => Mongoid 8: <BSON::Binary:0x2600 type=generic data=0x6461746121...>
Removed Document#to_a
Method
The previously deprecated Document#to_a
method has been removed in
Mongoid 8.
Removed :drop_dups
Option from Indexes
The :drop_dups
option has been removed from the index
macro. This option
was specific to MongoDB server 2.6 and earlier, which Mongoid no longer supports.
Removed Mongoid::Errors::EagerLoad
Exception Class
The previously deprecated Mongoid::Errors::EagerLoad
exception class
has been removed in Mongoid 8. It has not been used by Mongoid since
version 7.1.1 when eager loading for polymorphic belongs_to
associations
was implemented.
Removed Deprecated Constants
Mongoid 8 removes the following deprecated constants that are not expected to have been used outside of Mongoid:
Mongoid::Extensions::Date::EPOCH
Mongoid::Extensions::Time::EPOCH
Mongoid::Factory::TYPE
Removed Array#update_values
and Hash#update_values
methods
The previously deprecated Array#update_values
and Hash#update_values
methods have been removed in Mongoid 8.
Deprecated the geoHaystack
, geoSearch
Options
The geoHaystack
and geoSearch
options on indexes have been deprecated.
:id_sort
Option on #first/last
Removed
Support for the :id_sort
option on the #first
and #last
options has
been dropped. These methods now only except a limit as a positional argument.
Mongoid::Criteria Cache Removed
Support for individually caching criteria objects has been dropped in Mongoid 8.
In order to get caching functionality, enable the Mongoid Query Cache. See the section on Query Cache for more details.