Docs Menu

Docs HomeMongoid

Mongoid 8.0

On this page

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.

Mongoid 8 requires MongoDB 3.6 or newer. Earlier server versions are not supported.

Mongoid 8 requires Ruby 2.6 or newer. Earlier Ruby versions are not supported.

Mongoid 8 requires Rails 5.2 or newer. Earlier Rails versions are not supported.

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.

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.

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 table shows the changes in functionality:

Field Type
Situation
Previous Functionality
New Functionality
Boolean
When a non-boolean string is assigned: "bogus value"
return false
return nil
Array/Hash
When a value that is not an array or hash is assigned
raise InvalidValue error
return nil
Set
When a value that is not a set is assigned: 1
raise NoMethodError Exception: undefined method to_a for 1:Integer
return nil
Regexp
When persisting and reading a Regexp from the database
return a BSON::Regexp::Raw
return a Regexp
Time/DateTime
When assigning a bogus value: :bogus
raise NoMethodError Exception: undefined method to_i for :bogus:Symbol
return nil
Time/DateTime
When demongoizing a non-Time value: "bogus", Date.today
raise NoMethodError Exception: undefined method getlocal for "bogus":String

"bogus": return nil

Date.today: return Time/DateTime

Date
When assigning or demongoizing a bogus value: :bogus
raise NoMethodError Exception: undefined method year for :bogus:Symbol
return nil
Time/DateTime/Date
When demongoizing a valid string: "2022-07-14 14:45:51 -0400"
raise NoMethodError Exception: undefined method getlocal for "2022-07-14 14:45:51 -0400":String
return a Time/DateTime/Date
All Types
When an uncastable value is assigned or demongoized
undefined behavior, occasionally raise NoMethodError
return nil
All Types
When an uncastable value is evolved
undefined behavior, occasionally raise NoMethodError
return inputted value

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.

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 the attributes hash. If parameters are passed to the initializer, those values will be stored in the attributes_before_type_cast hash before they are mongoized.

  • When assigning a value to the model, the mongoized value (i.e. when assiging '1' to an Integer field, it is mongoized to 1) is stored in the attributes hash, whereas the raw value (i.e. '1') is stored in the attributes_before_type_cast hash.

  • When saving, creating (i.e. using the create! method), or reloading the model, the attributes_before_type_cast hash is reset to have the same contents as the attributes 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 their demongoized form.

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

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.

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

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

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>

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]

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.

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.

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.

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">

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.

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.

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...>

The previously deprecated Document#to_a method has been removed in Mongoid 8.

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.

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.

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

The previously deprecated Array#update_values and Hash#update_values methods have been removed in Mongoid 8.

The geoHaystack and geoSearch options on indexes have been deprecated.

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.

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.

←  Mongoid 8.1Mongoid 7.5 →