Docs Menu

Docs HomeMongoid

Mongoid 7.4

On this page

  • Ruby Version Support
  • Support for MongoDB 3.4 and Earlier Servers Deprecated
  • Feature Flags Summary
  • Change === Operator To Match Ruby Semantics
  • Return String _id Value (Hexadecimal) from BSON::ObjectId#as_json
  • Scoped Associations
  • Compare Times With Millisecond Precision When Embedded Matching
  • count, sum, avg, min, max Ignore Sort If Not Limiting/Skipping
  • Return 0 When Aggregating Empty Result Sets
  • Correct Update Behavior When Replacing Association
  • Correct Logical and Query Generation
  • Restore Parent Scope When Exiting with_scope Block
  • Changes to distinct and pluck
  • Respect Field Aliases In Embedded Documents When Using distinct and pluck
  • Demongoize Values Returned from pluck and distinct
  • Localized Fields with pluck and distinct
  • Embedded Fields with pluck
  • update_one Warnings in upsert

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.

As of version 7.4, Mongoid supports Ruby 2.5+. Support for Ruby 2.4 and earlier has been dropped.

Mongoid 7.4 deprecates support for MongoDB 3.4 and earlier. Mongoid 8 will require MongoDB 3.6 or newer.

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 configuration options in your mongoid.yml file. We recommend newly created apps to do this as well.

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

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:

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:

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:

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:

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.

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:

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.

Associations now support the :scope argument, yielding scoped associations.

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.

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:

Band.desc(:name).count

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:

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.

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:

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

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:

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:

#<Mongoid::Criteria
selector: {"_id"=>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:

<Mongoid::Criteria
selector: {"_id"=>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.

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:

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:

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.

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:

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:

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

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

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:

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:

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:

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.

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:

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:

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:

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.

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:

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:

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:

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.

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.

←  Mongoid 7.5Mongoid 7.3 →