*********** Mongoid 7.4 *********** .. default-domain:: mongodb .. contents:: On this page :local: :backlinks: none :depth: 2 :class: singlecol This page describes significant changes and improvements in Mongoid 7.4. 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. All behavior changes in Mongoid 7.4 must be explicitly requested by changing the value of configuration options as detailed below. By default, Mongoid 7.4 behaves the same as Mongoid 7.3. Ruby Version Support -------------------- As of version 7.4, Mongoid supports Ruby 2.5+. Support for Ruby 2.4 and earlier has been dropped. Support for MongoDB 3.4 and Earlier Servers Deprecated ------------------------------------------------------ Mongoid 7.4 deprecates support for MongoDB 3.4 and earlier. Mongoid 8 will require MongoDB 3.6 or newer. Feature Flags Summary --------------------- To ensure a stable upgrade path from Mongoid 7.3, Mongoid 7.4 introduces feature flags which are further explained in the sections below. To enable all new behavior in Mongoid 7.4, please use the following :ref:`configuration options ` in your mongoid.yml file. We recommend newly created apps to do this as well. .. code-block:: yaml development: ... options: # Enable all new behavior in Mongoid 7.4 legacy_triple_equals: false object_id_as_json_oid: false compare_time_by_ms: true broken_aggregables: false broken_updates: false broken_and: false broken_scoping: false broken_alias_handling: false legacy_pluck_distinct: false Change ``===`` Operator To Match Ruby Semantics ----------------------------------------------- In Mongoid 7.4, the ``===`` operator on ``Mongoid::Document`` classes and instances can be configured to behave the same way as it does in Ruby, and is equivalent to calling ``is_a?`` on the right hand side with the left hand side as the argument: .. code-block:: ruby ModelClass === instance # equivalent to: instance.is_a?(ModelClass) In order to get this functionality, the ``Mongoid.legacy_triple_equals`` option must be set to false. If it is set to true, which is the default for Mongoid 7.4, the ``===`` operator will function as it did in Mongoid 7.3: ``===`` returned ``true`` for some cases when the equivalent Ruby ``===`` implementation returned false, as per the examples below. Mongoid 7.4 with ``Mongoid.legacy_triple_equals`` set to ``false`` behavior: .. code-block:: ruby class Band include Mongoid::Document has_many :members end class CoverBand < Band end class Member include Mongoid::Document belongs_to :band end band = Band.new cover_band = CoverBand.new band === Band # => false cover_band === Band # => false Band === Band # => false CoverBand === Band # => false band.members === Array # => false band.members === Mongoid::Association::Referenced::HasMany::Enumerable # => false Mongoid 7.3 and 7.4 with ``Mongoid.legacy_triple_equals`` set to ``true`` behavior: .. code-block:: ruby band === Band # => true cover_band === Band # => true Band === Band # => true CoverBand === Band # => true band.members === Array # => true band.members === Mongoid::Association::Referenced::HasMany::Enumerable # => true The standard invocation of ``===``, that is having the class on the left and the instance on the right, works the same in Mongoid 7.4 as it did previously and matches the core Ruby behavior: .. code-block:: ruby Band === band # => true Band === cover_band # => true .. note:: In Mongoid 8.0, the default value of the ``Mongoid.legacy_triple_equals`` option will change to ``false``. Return String ``_id`` Value (Hexadecimal) from ``BSON::ObjectId#as_json`` ------------------------------------------------------------------------- Mongoid 7.4 permits configuring the ``BSON::ObjectId#as_json`` method to return the ``_id`` value as a hexadecimal string instead of the ``{"$oid" => "..."}`` hash it has returned in Mongoid 7.3 and previous versions. When ``Mongoid.object_id_as_json_oid`` is set to ``false``, Mongoid will delegate to ``bson-ruby`` implementation of ``BSON::ObjectId#as_json``. In ``bson-ruby`` 4 the ``BSON::ObjectId#as_json`` method will continue to return the hash ``{"$oid" => "..."}`` for backwards compatibility, but in ``bson-ruby`` 5 the ``BSON::ObjectId#as_json`` method will return only the hexadecimal ObjectId string. When ``Mongoid.object_id_as_json_oid`` is set to ``true``, Mongoid will install an implementation of ``BSON::ObjectId#as_json`` which returns the hash ``{"$oid" => "..."}`` as it did in Mongoid 7.3 and earlier. The behavior of ``as_json`` is summarized in the following table: .. list-table:: :header-rows: 1 :stub-columns: 1 :class: compatibility-large no-padding * - ``Mongoid.object_id_as_json_oid`` value - true - false * - ``bson-ruby`` 4 - ``{"$oid"=>"621ed7fda15d5d231594627c"}`` - ``{"$oid"=>"621ed7fda15d5d231594627c"}`` * - ``bson-ruby`` 5 - ``{"$oid"=>"621ed7fda15d5d231594627c"}`` - ``"621ed7fda15d5d231594627c"`` .. note:: In Mongoid 8.0, the default value of the ``Mongoid.object_id_as_json_oid`` option will change to ``false``. Scoped Associations ------------------- Associations now support the ``:scope`` argument, yielding :ref:`scoped associations `. Compare Times With Millisecond Precision When Embedded Matching --------------------------------------------------------------- Mongoid 7.4 with the ``Mongoid.compare_time_by_ms`` option set to ``true`` will truncate the times to millisecond precision when comparing them while performing embedded matching. Time objects in Ruby have nanosecond precision, whereas MongoDB server can only store times with millisecond precision. Set the ``Mongoid.compare_time_by_ms`` option to ``true`` to truncate times to millisecond precision when performing queries on already loaded embedded associations (this is also called "embedded matching" and is done completely in Ruby), to obtain the same query results when performing time comparisons regardless of which documents are being queried. Setting this option to ``false`` will produce different results for queries on embedded associations that are already loaded into memory vs queries on unloaded associations and top-level models. The ``Mongoid.compare_time_by_ms`` option is set to ``false`` by default in Mongoid 7.4 for backwards compatibility. .. note:: In Mongoid 8.0, the default value of the ``Mongoid.compare_time_by_ms`` option will change to ``true``. ``count``, ``sum``, ``avg``, ``min``, ``max`` Ignore Sort If Not Limiting/Skipping ---------------------------------------------------------------------------------- The ``count``, ``sum``, ``avg``, ``min`` and ``max`` methods now omit the sort stage from the generated aggregation pipeline if no skip or limit is specified, because the results aren't affected by the sort order. Example call that will now omit the sort stage and would potentially use an index where it wouldn't before: .. code-block:: ruby Band.desc(:name).count Return ``0`` When Aggregating Empty Result Sets ----------------------------------------------- Mongoid 7.4 with the ``Mongoid.broken_aggregables`` option set to ``false`` will return ``0`` from the ``sum`` method over an empty result set, for example: .. code-block:: ruby Product.where(impossible_condition: true).sum(:price) # => 0 Mongoid 7.3 and Mongoid 7.4 with the ``Mongoid.broken_aggregables`` option set to ``true`` (the default) returns ``nil`` in this case. .. note:: In Mongoid 8.0, the default value of the ``Mongoid.broken_aggregables`` option will change to ``false``. Correct Update Behavior When Replacing Association -------------------------------------------------- Mongoid 7.4 with the ``Mongoid.broken_updates`` option set to ``false`` will correctly persist an ``embeds_one`` association target that is set to nil and then to a non-nil value, for example: .. code-block:: ruby class Canvas include Mongoid::Document embeds_one :palette end canvas.palette = palette canvas.palette = nil canvas.palette = palette In Mongoid 7.3 and earlier, and in 7.4 with the ``Mongoid.broken_aggregables`` option set to ``true`` (the default), ``canvas.palette`` would be ``nil`` when we would expect it to be ``palette``. .. note:: In Mongoid 8.0, the default value of the ``Mongoid.broken_updates`` option will change to ``false`` Correct Logical ``and`` Query Generation ---------------------------------------- Mongoid 7.4 with the ``Mongoid.broken_and`` option set to ``false`` will preserve existing conditions when using ``and`` to add new conditions to a query when the same operator is used on the same field multiple times. For example, in the following query: .. code-block:: ruby Band.where(id: 1).and({year: {'$in' => [2020]}}, {year: {'$in' => [2021]}}).where(id: 2) Mongoid 7.4 with the ``Mongoid.broken_and`` option set to ``false`` will generate the following criteria: .. code-block:: ruby #1, "year"=>{"$in"=>[2020]}, "$and"=>[{"year"=>{"$in"=>[2021]}}, {"_id"=>2}]} options: {} class: Band embedded: false> In Mongoid 7.3 and earlier, and in 7.4 with the ``Mongoid.broken_and`` option set to ``true`` (the default), the following criteria would be generated instead which omit the {"$in" => [2021]} condition: .. code-block:: ruby 1, "year"=>{"$in"=>[2020]}, "$and"=>[{"_id"=>2}]} options: {} class: Band embedded: false> .. note:: In Mongoid 8.0, the default value of the ``Mongoid.broken_and`` option will change to ``false``. Restore Parent Scope When Exiting ``with_scope`` Block ------------------------------------------------------ Mongoid 7.4 with the ``Mongoid.broken_scoping`` option set to ``false`` will restore the parent scope when exiting a ``with_scope`` block. For example: .. code-block:: ruby Band.with_scope(year: 2020) do Band.with_scope(active: true) do # ... end # {year: 2020} condition is applied here end In Mongoid 7.3 and earlier, and in 7.4 with the ``Mongoid.broken_scoping`` option set to ``true`` (the default), once any ``with_scope`` block finishes, all scopes are cleared: .. code-block:: ruby Band.with_scope(year: 2020) do Band.with_scope(active: true) do # ... end # No scope is applied here end .. note:: In Mongoid 8.0, the default value of the ``Mongoid.broken_scoping`` option will change to ``false``. Changes to ``distinct`` and ``pluck`` ------------------------------------- Respect Field Aliases In Embedded Documents When Using ``distinct`` and ``pluck`` ````````````````````````````````````````````````````````````````````````````````` When ``distinct`` and ``pluck`` are used with aliased fields in embedded documents, the aliases can be expanded if the ``Mongoid.broken_alias_handling`` option is set to ``false``. By default, for backwards compatibility, in Mongoid 7.4 this option is set to true, yielding Mongoid 7.3 and earlier behavior. Given the following definitions: .. code-block:: ruby class Band include Mongoid::Document embeds_many :managers end class Manager include Mongoid::Document embedded_in :band field :name, as: :n end Mongoid 7.4 behavior with ``Mongoid.broken_alias_handling`` set to ``false``: .. code-block:: ruby # Expands out to "managers.n" in the query: Band.distinct('managers.name') Band.pluck('managers.name') Mongoid 7.3 and 7.4 with ``Mongoid.broken_alias_handling`` set to ``true`` behavior: .. code-block:: ruby # Sends "managers.name" without expanding the alias: Band.distinct('managers.name') Band.pluck('managers.name') .. note:: The alias expansion for top-level fields has already been done by Mongoid 7.3. .. note:: In Mongoid 8.0, the default value of the ``Mongoid.broken_alias_handling`` option will change to ``false``. Demongoize Values Returned from ``pluck`` and ``distinct`` `````````````````````````````````````````````````````````` Mongoid 7.4 with the ``Mongoid.legacy_pluck_distinct`` option set to ``false`` will demongoize values returned from the ``pluck`` and ``distinct`` methods. Given the following definitions: .. code-block:: ruby class Band include Mongoid::Document field :sales, type: BigDecimal end Band.create!(sales: "1E2") Band.create!(sales: "2E2") Mongoid 7.4 behavior with ``Mongoid.legacy_pluck_distinct`` set to ``false``: .. code-block:: ruby Band.pluck(:sales) # => [0.1e3, 0.2e3] Band.distinct(:sales) # => [0.1e3, 0.2e3] In Mongoid 7.3 and earlier, and in 7.4 with the ``Mongoid.legacy_pluck_distinct`` option set to ``true`` (the default), the value returned from the pluck and distinct methods will not be demongoized. For example: .. code-block:: ruby Band.pluck(:sales) # => ["1E2", "2E2"] Band.distinct(:sales) # => ["1E2", "2E2"] .. note:: In Mongoid 8.0, the default value of the ``Mongoid.legacy_pluck_distinct`` option will change to ``false``. Localized Fields with ``pluck`` and ``distinct`` ```````````````````````````````````````````````` Mongoid 7.4 with the ``Mongoid.legacy_pluck_distinct`` option set to ``false`` changes the behavior of using ``pluck`` and ``distinct`` with localized fields. Now, when retrieving a localized field using these methods, the translation for the current locale will be returned. To get the full translations hash the ``_translations`` field can be used. Given the following definitions: .. code-block:: ruby class Band include Mongoid::Document field :name, localize: true end I18n.locale = :en band = Band.create!(name: 'english-name') I18n.locale = :de band.name = 'deutsch-name' band.save! Mongoid 7.4 behavior with ``Mongoid.legacy_pluck_distinct`` set to ``false``: .. code-block:: ruby Band.pluck(:name) # => ["deutsch-name"] Band.pluck(:name_translations) # => [{"en"=>"english-name", "de"=>"deutsch-name"}, {"en"=>"english-name", "de"=>"deutsch-name"}] In Mongoid 7.3 and earlier, and in 7.4 with the ``Mongoid.legacy_pluck_distinct`` option set to ``true`` (the default), inputting a localized field returns the full translations hash. Inputting the ``_translations`` field will return ``nil``. For example: .. code-block:: ruby Band.pluck(:name) # => [{"en"=>"english-name", "de"=>"deutsch-name"}, {"en"=>"english-name", "de"=>"deutsch-name"}] Band.pluck(:name_translations) # => [nil, nil] .. note:: In Mongoid 8.0, the default value of the ``Mongoid.legacy_pluck_distinct`` option will change to ``false``. Embedded Fields with ``pluck`` `````````````````````````````` Mongoid 7.4 with the ``Mongoid.legacy_pluck_distinct`` option set to ``false`` returns the embedded values themselves, i.e. not inside a hash. Given the following definitions: .. code-block:: ruby class Band include Mongoid::Document embeds_one :label end class Label include Mongoid::Document embedded_in :band field :sales, type: BigDecimal end Mongoid 7.4 behavior with ``Mongoid.legacy_pluck_distinct`` set to ``false``: .. code-block:: ruby Band.pluck("label.sales") # => [0.1e3] In Mongoid 7.3 and earlier, and in 7.4 with the ``Mongoid.legacy_pluck_distinct`` option set to ``true`` (the default), plucking embedded attributes returns them inside a hash. For example: .. code-block:: ruby Band.pluck("label.sales") # => [{"sales"=>"1E2"}] .. note:: In Mongoid 8.0, the default value of the ``Mongoid.legacy_pluck_distinct`` option will change to ``false``. ``update_one`` Warnings in ``upsert`` ------------------------------------- Mongoid 7.4.1 fixes incorrect usage of the driver's ``update_one`` method from Mongoid's ``upsert`` method. Mongoid's ``upsert`` actually performs a replacing upsert, and Mongoid 7.4.1 and later correctly call ``replace_one``.