123456789_123456789_123456789_123456789_123456789_

Mongoid 8.1

This page describes significant changes and improvements in Mongoid 8.1. 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.

Added load_async method on Criteria to asynchronously load documents

The new load_async method on Criteria allows 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:

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:

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:

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:

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

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:

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

This is helpful for upgrading between versions. See the section on 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:

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

See the section on 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:

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:

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

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

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

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:

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:

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:

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

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.

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

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