*********** Mongoid 8.0 *********** .. default-domain:: mongodb .. contents:: On this page :local: :backlinks: none :depth: 2 :class: singlecol 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 :ref:`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 :ref:`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 :ref:`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 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. .. code-block:: ruby 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: .. code-block:: ruby # 2 Mongoid 7 output: .. code-block:: ruby # 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: .. code-block:: ruby 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: .. code-block:: ruby class User include Mongoid::Document field :name, type: :bogus # => raises Mongoid::Errors::InvalidFieldType end Mongoid 7 behavior: .. code-block:: ruby 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: .. code-block:: ruby Band.any_of({name: 'The Rolling Stones'}, {founded: 1990}). any_of({members: 2}, {last_tour: 1995}) # => # #[{"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}) # => # #[{"name"=>"The Rolling Stones"}, {"founded"=>1990}], "members"=>2} # options: {} # class: Band # embedded: false> Mongoid 7 behavior: .. code-block:: ruby Band.any_of({name: 'The Rolling Stones'}, {founded: 1990}). any_of({members: 2}, {last_tour: 1995}) # => # #[{"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}) # => # #[{"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: .. code-block:: ruby 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: .. code-block:: ruby # 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: .. code-block:: ruby # 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: .. code:: class User include Mongoid::Document field :age end and the following documents in the database: .. code:: { _id: 1, age: 21 } { _id: 2, age: 21 } { _id: 3, age: 22 } Calling ``tally`` on the age field yields the following: .. code:: 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: .. code:: class User include Mongoid::Document field :age end and the following documents in the database: .. code:: { _id: 1, age: 21 } { _id: 2, age: 21 } { _id: 3, age: 22 } Calling ``pick`` on the age field yields the following: .. code:: 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: .. code:: 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``: .. code:: Band.find do |b| b.name == "Depeche Mode" end # => # 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: .. code:: 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: .. code:: child = Child.new parent = Parent.new child.parent = parent # parent is invalid, it does not have a name child.save p Child.first # => 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: .. code:: 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: .. code:: class Registry include Mongoid::Document field :data, type: BSON::Binary end registry = Registry.create!(data: "data!") p registry.data # => Mongoid 7: "data!" # => Mongoid 8: registry = Registry.find(registry.id) p registry.data # => Mongoid 7: "data!" # => Mongoid 8: 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 :ref:`Query Cache ` for more details.