mongodb/mongoid

View on GitHub
docs/release-notes/mongoid-8.1.txt

Summary

Maintainability
Test Coverage
***********
Mongoid 8.1
***********

.. default-domain:: mongodb

.. contents:: On this page
   :local:
   :backlinks: none
   :depth: 2
   :class: singlecol

This page describes significant changes and improvements in Mongoid 8.1.
The complete list of releases is available `on GitHub
<https://github.com/mongodb/mongoid/releases>`_ and `in JIRA
<https://jira.mongodb.org/projects/MONGOID?selectedItem=com.atlassian.jira.jira-projects-plugin:release-page>`_;
please consult GitHub releases for detailed release notes and JIRA for
the complete list of issues fixed in each release, including bug fixes.

Added ``load_async`` method on ``Criteria`` to asynchronously load documents
----------------------------------------------------------------------------

The new ``load_async`` method on ``Criteria`` allows :ref:`running database queries asynchronously <load-async>`.


Added ``attribute_before_last_save``, ``saved_change_to_attribute``, ``saved_change_to_attribute?``, and ``will_save_change_to_attribute?``  methods
----------------------------------------------------------------------------------------------------------------------------------------------------

These new methods behave identically to corresponding methods
from ``ActiveRecord::AttributeMethods::Dirty``. The methods are particularly useful in
callbacks:

.. code-block:: ruby

  class Person
    include Mongoid::Document

    field :name, type: String

    before_save do
      puts "attribute_was(:name): #{attribute_was(:name)}"
      puts "attribute_before_last_save(:name): #{attribute_before_last_save(:name)}"
      puts "will_save_change_to_attribute?(:name): #{will_save_change_to_attribute?(:name)}"
    end

    after_save do
      puts "attribute_was(:name): #{attribute_was(:name)}"
      puts "attribute_before_last_save(:name): #{attribute_before_last_save(:name)}"
      puts "saved_change_to_attribute(:name): #{saved_change_to_attribute(:name)}"
      puts "attribute_changed?(:name): #{attribute_changed?(:name)}"
      puts "saved_change_to_attribute?(:name): #{saved_change_to_attribute?(:name)}"
    end
  end

  person = Person.create(name: 'John')
  #
  # before_save
  #
  ## attribute_was(:name): nil
  ## attribute_before_last_save(:name): nil
  ## will_save_change_to_attribute?(:name): true
  #
  # after_save
  #
  ## attribute_was(:name): John => New value
  ## attribute_before_last_save(:name): nil => Value before save
  ## saved_change_to_attribute(:name): [nil, "John"] => Both values
  ## attribute_changed?(:name): false
  ## saved_change_to_attribute?(:name): true => Correctly indicates that the change for :name was saved

  person.name = 'Jane'
  person.save
  #
  # before_save
  #
  ## attribute_was(:name): John => attribute_was not look back before the last save
  ## attribute_before_last_save(:name): nil => value before the last save
  ## will_save_change_to_attribute?(:name): true
  #
  # after_save
  #
  ## attribute_was(:name): Jane => New value
  ## attribute_before_last_save(:name): John => Value before save
  ## saved_change_to_attribute(:name): ["John", "Jane"] => Both values
  ## attribute_changed?(:name): false
  ## saved_change_to_attribute?(:name): true => Correctly indicates that the change for :name was saved

For all of the new methods there are also shorter forms created dynamically, e.g.
``attribute_before_last_save(:name)`` is equivalent to ``name_before_last_save``,
``saved_change_to_attribute(:name)`` is equivalent to ``saved_change_to_name``,
``saved_change_to_attribute?(:name)`` is equivalent to ``saved_change_to_name?``,
and ``will_save_change_to_attribute?(:name)`` is equivalent to ``will_save_change_to_name?``.


Deprecated ``use_activesupport_time_zone`` config option
--------------------------------------------------------

The config option ``use_activesupport_time_zone`` has been deprecated.
Beginning in Mongoid 9.0, it will be ignored and always behave as true.
Since ``use_activesupport_time_zone`` currently defaults to true, it is
safe to remove from your config file at this time.


Configuration DSL No Longer Requires an Argument to its Block
-------------------------------------------------------------

It is now possible to use ``Mongoid.configure`` without
providing an argument to its block:

.. code-block:: ruby

  Mongoid.configure do
    connect_to("mongoid_test")

    # Use config method when assigning variables
    config.preload_models = true

Note that ``configure`` will continue to support a block argument.
The following is equivalent to the above:

.. code-block:: ruby

  Mongoid.configure do |config|
    config.connect_to("mongoid_test")

    config.preload_models = true


Added ``Mongoid::Criteria`` finder methods
------------------------------------------

Mongoid 8.1 implements several finder methods on ``Mongoid::Criteria``:

- ``first!``
- ``last!``
- ``second/second!``
- ``third/third!``
- ``fourth/fourth!``
- ``fifth/fifth!``
- ``second_to_last/second_to_last!``
- ``third_to_last/third_to_last!``

When no documents are found, methods without a bang (!) return nil, and methods
with a bang (!) raise an error:

.. code:: ruby

  Band.none.first
  # => nil

  Band.none.first!
  # => raise Mongoid::Errors::DocumentNotFound


Added ``:touch`` option to ``#save``
------------------------------------

Support for the ``:touch`` option has been added to the ``#save`` and ``#save!``
methods. When this option is false, the ``updated_at`` field on the saved
document and all of it's embedded documents will not be updated with the
current time. When this option is true or unset, the ``updated_at`` field will
be updated with the current time.

If the document being saved is a new document (i.e. it has not yet been
persisted to the database), then the ``:touch`` option will be ignored, and the
``updated_at`` (and ``created_at``) fields will be updated with the current
time.


Added Version Based Default Configuration
-----------------------------------------

Mongoid 8.1 has added the ability to set the default configurations for a
specific version:

.. code:: ruby

  Mongoid.configure do |config|
    config.load_defaults 8.0
  end

This is helpful for upgrading between versions. See the section on
:ref:`Version Based Default Configuration <load-defaults>` for more details on
how to use this feature to make upgrading between Mongoid versions easier.


Added ``:present`` option to localized fields
---------------------------------------------

The ``:present`` option was added to localized fields for removing blank values
from the ``_translations`` hash:

.. code-block:: ruby

   class Product
     include Mongoid::Document
     field :description, localize: :present
   end

See the section on :ref:`Localize :present Field Option <present-fields>` for
more details on how these are used.


Added ``:to`` and ``:from`` options to ``attribute_changed?``
-------------------------------------------------------------

Mongoid 8.1 adds the ``:to`` and ``:from`` options on the ``attribute_changed?``
method. These options can be used to inquire whether the attribute has been changed
to or from a certain value:

.. code:

  class Person
    include Mongoid::Document
    field :name, type: String
  end

  person = Person.create!(name: "Trout")
  person.name = "Ohtani"

  person.name_changed?
  # => true
  person.name_changed?(from: "Trout")
  # => true
  person.name_changed?(to: "Ohtani")
  # => true
  person.name_changed?(from: "Trout", to: "Ohtani")
  # => true
  person.name_changed?(from: "Trout", to: "Fletcher")
  # => false


Allowed ``store_in`` to be called on subclasses
-----------------------------------------------

Starting in Mongoid 8.1, subclasses can now specify which collection its
documents should be stored in using the ``store_in`` macro:

.. code:: ruby

  class Shape
    include Mongoid::Document
    store_in collection: :shapes
  end

  class Circle < Shape
    store_in collection: :circles
  end

  class Square < Shape
    store_in collection: :squares
  end

Previously, an error was raised if this was done. See the section on
:ref:`Inheritance Persistence Context <inheritance-persistence-context>`
for more details.


Added ``readonly!`` method and ``legacy_readonly`` feature flag
---------------------------------------------------------------

Mongoid 8.1 changes the meaning of read-only documents. In Mongoid 8.1 with
this feature flag turned off (``false``), a document becomes read-only when calling the
``readonly!`` method:

.. code:: ruby

  band = Band.first
  band.readonly? # => false
  band.readonly!
  band.readonly? # => true
  band.name = "The Rolling Stones"
  band.save # => raises ReadonlyDocument error

With this feature flag turned off, a ``ReadonlyDocument`` error will be
raised when destroying or deleting, as well as when saving or updating.

Prior to Mongoid 8.1 and in 8.1 with the ``legacy_readonly`` feature flag
turned on (``true``), documents become read-only when they are projected (i.e. using
``#only`` or ``#without``).

.. code:: ruby

  class Band
    include Mongoid::Document
    field :name, type: String
    field :genre, type: String
  end

  band = Band.only(:name).first
  band.readonly? # => true
  band.destroy # => raises ReadonlyDocument error

Note that with this feature flag on, a ``ReadonlyDocument`` error will only be
raised when destroying or deleting, and not on saving or updating. See the
section on :ref:`Read-only Documents <readonly-documents>` for more details.


Added method parameters to ``#exists?``
---------------------------------------

Mongoid 8.1 introduces method paramters to the ``Contextual#exists?`` method.
An _id, a hash of conditions, or ``false``/``nil`` can now be included:

.. code:: ruby

  Band.exists?
  Band.exists?(name: "The Rolling Stones")
  Band.exists?(BSON::ObjectId('6320d96a3282a48cfce9e72c'))
  Band.exists?(false) # always false
  Band.exists?(nil) # always false


Added ``:replace`` option to ``#upsert``
----------------------------------------

Mongoid 8.1 adds the ``:replace`` option to the ``#upsert`` method. This option
is ``false`` by default.

In Mongoid 8 and earlier, and in Mongoid 8.1 when passing ``replace: true``
(the default) the upserted document will overwrite the current document in the
database if it exists. Consider the following example:

.. code:: ruby

  existing = Player.create!(name: "Juan Soto", age: 23, team: "WAS")

  player = Player.new(name: "Juan Soto", team: "SD")
  player.id = existing.id
  player.upsert # :replace defaults to true in 8.1

  p Player.find(existing.id)
  # => <Player _id: 633b42f43282a45fadfaaf9d, name: "Juan Soto", age: nil, team: "SD">

As you can see, the value for the ``:age`` field was dropped, because the
upsert replaced the entire document instead of just updating it. If we take the
same example and set ``:replace`` to ``false``, however:

.. code:: ruby

  player.upsert(replace: false)

  p Player.find(existing.id)
  # => <Player _id: 633b42f43282a45fadfaaf9d, name: "Juan Soto", age: 23, team: "SD">

This time, the value for the ``:age`` field is maintained.

.. note::

  The default for the ``:replace`` option will be changed to ``false`` in
  Mongoid 9.0, therefore it is recommended to explicitly specify this option
  while using ``#upsert`` in 8.1 for easier upgradability.


Allow Hash Assignment to ``embedded_in``
----------------------------------------

Mongoid 8.1 allows the assignment of a hash to an ``embedded_in`` association.
On assignment, the hash will be coerced into a document of the class of the
association that it is being assigned to. This functionality already exists
for ``embeds_one`` and ``embeds_many`` associations. Consider the following
example:

.. code:: ruby

  class Band
    include Mongoid::Document
    field :name, type: String
    embeds_many :albums
  end

  class Album
    include Mongoid::Document
    embedded_in :band
  end

  album = Album.new
  album.band = { name: "Death Cab For Cutie" }
  p album.band
  # => <Band _id: 633c74113282a438a15d2b56, name: "Death Cab For Cutie">

See the section on :ref:`Hash Assignment on Embedded Associations <hash-assignment>`
for more details


Added ``none_of`` Query Method
------------------------------

With the addition of ``none_of``, Mongoid 8.1 allows queries to exclude
conditions in bulk. The emitted query will encapsulate the specified
criteria in a ``$nor`` operation. For example:

.. code:: ruby

  class Building
    include Mongoid::Document
    field :city, type: String
    field :height, type: Integer
    field :purpose, type: String
    field :occupancy, type: Integer
  end

  Building.where(city: 'Portland').
    none_of(:height.lt => 100,
            :purpose => 'apartment',
            :occupancy.gt => 2500)

This would query all buildings in Portland, excluding apartments, buildings less than
100 units tall, and buildings with an occupancy greater than 2500 people.


Added ``Mongoid::Config.immutable_ids``
---------------------------------------

Coming in Mongoid 9.0, the ``_id`` field will be immutable in both top-level
and embedded documents. This addresses some inconsistency in how mutations
to the ``_id`` field are treated currently. To prepare for this potentially
breaking change, the ``Mongoid::Config.immutable_ids`` flag has been added. It
defaults to ``false``, preserving the existing behavior, but you may set it to
``true`` to prepare your apps for Mongoid 9.0. When this is set to ``true``,
attempts to mutate the ``_id`` of a document will raise an exception.

.. code:: ruby

  # The default in Mongoid 8.1
  Mongoid::Config.immutable_ids = false

  # The default in Mongoid 9.0
  Mongoid::Config.immutable_ids = true