Navigation

Mongoid 8.0

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 => true
  • :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 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 a 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.

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

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.

←   Mongoid 8.1 Mongoid 7.5  →