diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000000..e06ff43896
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,19 @@
+# Security Policy
+
+## Supported Versions
+
+Use this section to tell people about which versions of your project are
+currently being supported with security updates.
+
+| Version | Supported |
+| ------- | ------------------ |
+| 7.3 | :white_check_mark: |
+| 7.2 | :white_check_mark: |
+| 7.1 | :white_check_mark: |
+| 7.0 | :white_check_mark: |
+| < 7.0 | :x: |
+
+## Reporting a Vulnerability
+
+Please [follow the instructions here](https://docs.mongodb.com/manual/tutorial/create-a-vulnerability-report/)
+to report a vulnerability.
diff --git a/docs/reference/configuration.txt b/docs/reference/configuration.txt
index 216225190b..0310ba4867 100644
--- a/docs/reference/configuration.txt
+++ b/docs/reference/configuration.txt
@@ -139,7 +139,7 @@ can be configured.
- myhost3.mydomain.com:27017
options:
# These options are Ruby driver options, documented in
- # https://docs.mongodb.com/ruby-driver/current/tutorials/ruby-driver-create-client/
+ # https://docs.mongodb.com/ruby-driver/current/reference/create-client/
# Change the default write concern. (default = { w: 1 })
write:
@@ -168,7 +168,13 @@ can be configured.
# on 2.4 and 2.6 is :plain)
auth_mech: :scram
- # The database or source to authenticate the user against. (default: admin)
+ # Specify the auth source, i.e. the database or other source which
+ # contains the user's login credentials. Allowed values for auth source
+ # depend on the authentication mechanism, as explained in the server documentation:
+ # https://docs.mongodb.com/manual/reference/connection-string/#mongodb-urioption-urioption.authSource
+ # If no auth source is specified, the default auth source as
+ # determined by the driver will be used. Please refer to:
+ # https://docs.mongodb.com/ruby-driver/current/reference/authentication/#auth-source
auth_source: admin
# Force the driver to connect in a specific way instead of auto-
@@ -291,7 +297,7 @@ can be configured.
use_utc: false
The Ruby driver options may be found in
-`the driver documentation `_.
+`the driver documentation `_.
ERb Preprocessing
=================
@@ -596,7 +602,7 @@ be executed sequentially during socket creation.
in an application.
For more information about TLS context hooks, including best practices for
-assigning and removing them, see `the Ruby driver documentation `_.
+assigning and removing them, see `the Ruby driver documentation `_.
Usage with Forking Servers
==========================
diff --git a/docs/reference/fields.txt b/docs/reference/fields.txt
index 810a415e9d..59fa144087 100644
--- a/docs/reference/fields.txt
+++ b/docs/reference/fields.txt
@@ -545,96 +545,6 @@ This is useful for storing different values in ``id`` and ``_id`` fields:
# => #
-Custom Fields
--------------
-
-You can define custom types in Mongoid and determine how they are serialized and deserialized.
-You simply need to provide three methods on it for Mongoid to call to convert your object to
-and from MongoDB friendly values.
-
-.. code-block:: ruby
-
- class Profile
- include Mongoid::Document
- field :location, type: Point
- end
-
- class Point
-
- attr_reader :x, :y
-
- def initialize(x, y)
- @x, @y = x, y
- end
-
- # Converts an object of this instance into a database friendly value.
- def mongoize
- [ x, y ]
- end
-
- class << self
-
- # Get the object as it was stored in the database, and instantiate
- # this custom class from it.
- def demongoize(object)
- Point.new(object[0], object[1])
- end
-
- # Takes any possible object and converts it to how it would be
- # stored in the database.
- def mongoize(object)
- case object
- when Point then object.mongoize
- when Hash then Point.new(object[:x], object[:y]).mongoize
- else object
- end
- end
-
- # Converts the object that was supplied to a criteria and converts it
- # into a database friendly form.
- def evolve(object)
- case object
- when Point then object.mongoize
- else object
- end
- end
- end
- end
-
-The instance method ``mongoize`` takes an instance of your object, and converts it
-into how it will be stored in the database. In our example above, we want to store our
-point object as an array in the form ``[ x, y ]``.
-
-The class method ``demongoize`` takes an object as how it was stored in the database,
-and is responsible for instantiating an object of your custom type. In this case, we
-take an array and instantiate a ``Point`` from it.
-
-The class method ``mongoize`` takes an object that you would use to set on your model
-from your application code, and create the object as it would be stored in the database.
-This is for cases where you are not passing your model instances of your custom type in the setter:
-
-.. code-block:: ruby
-
- point = Point.new(12, 24)
- venue = Venue.new(location: point) # This uses the mongoize instance method.
- venue = Venue.new(location: [ 12, 24 ]) # This uses the mongoize class method.
-
-The class method ``evolve`` takes an object, and determines how it is to be transformed
-for use in criteria. For example we may want to write a query like so:
-
-.. code-block:: ruby
-
- point = Point.new(12, 24)
- Venue.where(location: point)
-
-Note that when accessing custom fields from the document, you will get a new instance
-of that object with each call to the getter. This is because Mongoid is generating a new
-object from the raw attributes on each access.
-
-We need the point object in the criteria to be transformed to a Mongo friendly value when
-it is not as well, ``evolve`` is the method that takes care of this. We check if the passed
-in object is a ``Point`` first, in case we also want to be able to pass in ordinary arrays instead.
-
Reserved Names
--------------
@@ -642,6 +552,7 @@ Attempting to define a field on a document that conflicts with a reserved
method name in Mongoid will raise an error. The list of reserved names can
be obtained by invoking the ``Mongoid.destructive_fields`` method.
+
Field Redefinition
------------------
@@ -729,6 +640,167 @@ alias can :ref:`be removed ` if desired (such as to integrate
with systems that use the ``id`` field to store value different from ``_id``.
+Customizing Field Behavior
+==========================
+
+Mongoid offers several options for customizing the behavior of fields.
+
+
+Custom Getters And Setters
+--------------------------
+
+You can define custom getters and setters for fields to modify the values
+when they are being accessed or written. The getters and setters use the
+same name as the field. Use ``read_attribute`` and ``write_attribute``
+methods inside the getters and setters to operate on the raw attribute
+values.
+
+For example, Mongoid provides the ``:default`` field option to write a
+default value into the field. If you wish to have a field default value
+in your application but do not wish to persist it, you can override the
+getter as follows:
+
+.. code-block:: ruby
+
+ class DistanceMeasurement
+ include Mongoid::Document
+
+ field :value, type: Float
+ field :unit, type: String
+
+ def unit
+ read_attribute(:unit) || "m"
+ end
+
+ def to_s
+ "#{value} #{unit}"
+ end
+ end
+
+ measurement = DistanceMeasurement.new(value: 2)
+ measurement.to_s
+ # => "2.0 m"
+ measurement.attributes
+ # => {"_id"=>BSON::ObjectId('613fa0b0a15d5d61502f3447'), "value"=>2.0}
+
+To give another example, a field which converts empty strings to nil values
+may be implemented as follows:
+
+.. code-block:: ruby
+
+ class DistanceMeasurement
+ include Mongoid::Document
+
+ field :value, type: Float
+ field :unit, type: String
+
+ def unit=(value)
+ if value.blank?
+ value = nil
+ end
+ write_attribute(:unit, value)
+ end
+ end
+
+ measurement = DistanceMeasurement.new(value: 2, unit: "")
+ measurement.attributes
+ # => {"_id"=>BSON::ObjectId('613fa15aa15d5d617216104c'), "value"=>2.0, "unit"=>nil}
+
+
+Custom Field Types
+------------------
+
+You can define custom types in Mongoid and determine how they are serialized
+and deserialized. You simply need to provide three methods on it for Mongoid
+to call to convert your object to and from MongoDB friendly values.
+
+.. code-block:: ruby
+
+ class Profile
+ include Mongoid::Document
+ field :location, type: Point
+ end
+
+ class Point
+
+ attr_reader :x, :y
+
+ def initialize(x, y)
+ @x, @y = x, y
+ end
+
+ # Converts an object of this instance into a database friendly value.
+ def mongoize
+ [ x, y ]
+ end
+
+ class << self
+
+ # Get the object as it was stored in the database, and instantiate
+ # this custom class from it.
+ def demongoize(object)
+ Point.new(object[0], object[1])
+ end
+
+ # Takes any possible object and converts it to how it would be
+ # stored in the database.
+ def mongoize(object)
+ case object
+ when Point then object.mongoize
+ when Hash then Point.new(object[:x], object[:y]).mongoize
+ else object
+ end
+ end
+
+ # Converts the object that was supplied to a criteria and converts it
+ # into a database friendly form.
+ def evolve(object)
+ case object
+ when Point then object.mongoize
+ else object
+ end
+ end
+ end
+ end
+
+The instance method ``mongoize`` takes an instance of your object, and
+converts it into how it will be stored in the database. In our example above,
+we want to store our point object as an array in the form ``[ x, y ]``.
+
+The class method ``demongoize`` takes an object as how it was stored in the
+database, and is responsible for instantiating an object of your custom type.
+In this case, we take an array and instantiate a ``Point`` from it.
+
+The class method ``mongoize`` takes an object that you would use to set on
+your model from your application code, and create the object as it would be
+stored in the database. This is for cases where you are not passing your
+model instances of your custom type in the setter:
+
+.. code-block:: ruby
+
+ point = Point.new(12, 24)
+ venue = Venue.new(location: point) # This uses the mongoize instance method.
+ venue = Venue.new(location: [ 12, 24 ]) # This uses the mongoize class method.
+
+The class method ``evolve`` takes an object, and determines how it is to be
+transformed for use in criteria. For example we may want to write a query
+like so:
+
+.. code-block:: ruby
+
+ point = Point.new(12, 24)
+ Venue.where(location: point)
+
+Note that when accessing custom fields from the document, you will get a
+new instance of that object with each call to the getter. This is because
+Mongoid is generating a new object from the raw attributes on each access.
+
+We need the point object in the criteria to be transformed to a
+MongoDB-friendly value when it is not as well, ``evolve`` is the method
+that takes care of this. We check if the passed in object is a ``Point``
+first, in case we also want to be able to pass in ordinary arrays instead.
+
+
.. _dynamic-fields:
Dynamic Fields
diff --git a/docs/reference/queries.txt b/docs/reference/queries.txt
index 86f3bb99b3..14d93da89b 100644
--- a/docs/reference/queries.txt
+++ b/docs/reference/queries.txt
@@ -1107,7 +1107,7 @@ the query cache. When using driver versions 2.14.0 or newer, this interface
will affect the driver query cache.
Read more about the Ruby driver query cache
-`in the driver documentation `_.
+`in the driver documentation `_.
.. warning::
@@ -1118,7 +1118,7 @@ Read more about the Ruby driver query cache
recommended that you upgrade to Ruby driver version 2.14.0 or newer.
Read more about the Ruby driver query cache
- `in the driver documentation `_.
+ `in the driver documentation `_.
Legacy Query Cache Limitations
------------------------------
@@ -1324,6 +1324,12 @@ Mongoid also has some helpful methods on criteria.
*Get a list of distinct values for a single field. Note this will always hit
the database for the distinct values.*
+
+ *This method accepts the dot notation, thus permitting referencing
+ fields in embedded associations.*
+
+ *This method respects :ref:`field aliases `,
+ including those defined in embedded documents.*
-
.. code-block:: ruby
@@ -1332,6 +1338,18 @@ Mongoid also has some helpful methods on criteria.
Band.where(:fans.gt => 100000).
distinct(:name)
+ Band.distinct('cities.name')
+
+ # Assuming an aliased field:
+ class Manager
+ include Mongoid::Document
+ embedded_in :band
+ field :name, as: :n
+ end
+
+ # Expands out to "managers.name" in the query:
+ Band.distinct('managers.n')
+
* - ``Criteria#each``
*Iterate over all matching documents in the criteria.*
@@ -1477,12 +1495,24 @@ Mongoid also has some helpful methods on criteria.
*Get all the values for the provided field.
Returns nil for unset fields and for non-existent fields.*
+
+ *This method accepts the dot notation, thus permitting referencing
+ fields in embedded associations.*
+
+ *This method respects :ref:`field aliases `,
+ including those defined in embedded documents.*
-
.. code-block:: ruby
Band.all.pluck(:name)
+ Band.all.pluck('cities.name')
+
+ # Using the earlier definition of Manager,
+ # expands out to "managers.name" in the query:
+ Band.all.pluck('managers.n')
+
Eager Loading
=============
diff --git a/docs/release-notes/mongoid-7.2.txt b/docs/release-notes/mongoid-7.2.txt
index ee354d7977..c01cf36f46 100644
--- a/docs/release-notes/mongoid-7.2.txt
+++ b/docs/release-notes/mongoid-7.2.txt
@@ -402,7 +402,7 @@ Mongoid query cache. If you plan to use the query cache, it is recommended
that you upgrade to driver version 2.14.
To read more about the query cache improvements made in the driver, see
-`the Ruby driver documentation `_.
+`the Ruby driver documentation `_.
To read more about using the query cache with Mongoid and the limitations
of the legacy query cache, see :ref:`the query cache documentation `.
diff --git a/docs/release-notes/mongoid-7.4.txt b/docs/release-notes/mongoid-7.4.txt
index 0e19c159f3..c5643eca37 100644
--- a/docs/release-notes/mongoid-7.4.txt
+++ b/docs/release-notes/mongoid-7.4.txt
@@ -111,3 +111,43 @@ which is `under consideration `_,
Mongoid 7.4 and later will inherit the new implementation provided by
``bson-ruby`` while Mongoid 7.3 and earlier will continue with the
implementation returning a hash of ``{"$oid" => "..."}``.
+
+
+``distinct`` and ``pluck`` Respect Field Aliases In Embedded Documents
+----------------------------------------------------------------------
+
+When ``distinct`` and ``pluck`` are used with aliased fields in embedded
+documents, the aliases are now expanded. 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:
+
+.. code-block:: ruby
+
+ # Expands out to "managers.name" in the query:
+ Band.distinct('managers.n')
+ Band.pluck('managers.n')
+
+Mongoid 7.3 behavior:
+
+.. code-block:: ruby
+
+ # Sends "managers.n" without expanding the alias:
+ Band.distinct('managers.n')
+ Band.pluck('managers.n')
+
+Note that the alias expansion for top-level fields has already been
+done by Mongoid 7.3.
diff --git a/lib/mongoid/association/macros.rb b/lib/mongoid/association/macros.rb
index 851a11b872..fa1088d2f8 100644
--- a/lib/mongoid/association/macros.rb
+++ b/lib/mongoid/association/macros.rb
@@ -12,9 +12,12 @@ module Macros
class_attribute :embedded, instance_reader: false
class_attribute :embedded_relations
class_attribute :relations
+ # @api private
+ class_attribute :aliased_associations
self.embedded = false
self.embedded_relations = BSON::Document.new
self.relations = BSON::Document.new
+ self.aliased_associations = {}
end
# This is convenience for libraries still on the old API.
@@ -193,6 +196,9 @@ def define_association!(macro_name, name, options = {}, &block)
Association::MACRO_MAPPING[macro_name].new(self, name, options, &block).tap do |assoc|
assoc.setup!
self.relations = self.relations.merge(name => assoc)
+ if assoc.respond_to?(:store_as) && assoc.store_as != name
+ self.aliased_associations[assoc.store_as] = name
+ end
end
end
end
diff --git a/lib/mongoid/criteria/queryable/selectable.rb b/lib/mongoid/criteria/queryable/selectable.rb
index 736e5845cd..fc26e55868 100644
--- a/lib/mongoid/criteria/queryable/selectable.rb
+++ b/lib/mongoid/criteria/queryable/selectable.rb
@@ -254,6 +254,26 @@ def geo_spacial(criterion)
end
key :within_box, :override, "$geoWithin", "$box"
+ # Add the $eq criterion to the selector.
+ #
+ # @example Add the $eq criterion.
+ # selectable.eq(age: 60)
+ #
+ # @example Execute an $eq in a where query.
+ # selectable.where(:field.eq => 10)
+ #
+ # @param [ Hash ] criterion The field/value pairs to check.
+ #
+ # @return [ Selectable ] The cloned selectable.
+ def eq(criterion)
+ if criterion.nil?
+ raise Errors::CriteriaArgumentRequired, :eq
+ end
+
+ __override__(criterion, "$eq")
+ end
+ key :eq, :override, "$eq"
+
# Add the $gt criterion to the selector.
#
# @example Add the $gt criterion.
diff --git a/lib/mongoid/fields.rb b/lib/mongoid/fields.rb
index 303fa68a8a..d9b072f2d8 100644
--- a/lib/mongoid/fields.rb
+++ b/lib/mongoid/fields.rb
@@ -252,18 +252,32 @@ def attribute_names
end
# Get the name of the provided field as it is stored in the database.
- # Used in determining if the field is aliased or not.
+ # Used in determining if the field is aliased or not. Recursively
+ # finds aliases for embedded documents and fields, delimited with
+ # period "." character.
#
- # @example Get the database field name.
+ # @example Get the database field name of a field.
# Model.database_field_name(:authorization)
#
+ # @example Get the database field name of an embedded field.
+ # Model.database_field_name('customers.addresses.city')
+ #
# @param [ String, Symbol ] name The name to get.
#
- # @return [ String ] The name of the field as it's stored in the db.
+ # @return [ String ] The name of the field as stored in the database.
def database_field_name(name)
- return nil unless name
- normalized = name.to_s
- aliased_fields[normalized] || normalized
+ return nil unless name.present?
+ key = name.to_s
+ segment, remaining = key.split('.', 2)
+ segment = aliased_fields[segment]&.dup || segment
+ return segment unless remaining
+
+ relation = relations[aliased_associations[segment] || segment]
+ if relation
+ "#{segment}.#{relation.klass.database_field_name(remaining)}"
+ else
+ "#{segment}.#{remaining}"
+ end
end
# Defines all the fields that are accessible on the Document
diff --git a/spec/integration/criteria/alias_query_spec.rb b/spec/integration/criteria/alias_query_spec.rb
new file mode 100644
index 0000000000..bb8095a79a
--- /dev/null
+++ b/spec/integration/criteria/alias_query_spec.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'distinct on aliased fields' do
+
+ let(:client) { Person.collection.client }
+
+ let(:subscriber) do
+ Mrss::EventSubscriber.new
+ end
+
+ before do
+ client.subscribe(Mongo::Monitoring::COMMAND, subscriber)
+ end
+
+ after do
+ client.unsubscribe(Mongo::Monitoring::COMMAND, subscriber)
+ end
+
+ let(:event) do
+ subscriber.single_command_started_event('distinct')
+ end
+
+ let(:command) { event.command }
+
+ context 'top level field' do
+ let(:query) do
+ Person.distinct(:test)
+ end
+
+ it 'expands the alias' do
+ query
+
+ command['key'].should == 't'
+ end
+ end
+
+ context 'embedded document field' do
+ let(:query) do
+ Person.distinct('phone_numbers.extension')
+ end
+
+ it 'expands the alias' do
+ query
+
+ command['key'].should == 'phone_numbers.ext'
+ end
+ end
+end
+
+describe 'pluck on aliased fields' do
+
+ let(:client) { Person.collection.client }
+
+ let(:subscriber) do
+ Mrss::EventSubscriber.new
+ end
+
+ before do
+ client.subscribe(Mongo::Monitoring::COMMAND, subscriber)
+ end
+
+ after do
+ client.unsubscribe(Mongo::Monitoring::COMMAND, subscriber)
+ end
+
+ let(:event) do
+ subscriber.single_command_started_event('find')
+ end
+
+ let(:command) { event.command }
+
+ context 'top level field' do
+ let(:query) do
+ Person.pluck(:test)
+ end
+
+ it 'expands the alias' do
+ query
+
+ command['projection'].should == {'t' => 1}
+ end
+ end
+
+ context 'embedded document field' do
+ let(:query) do
+ Person.pluck('phone_numbers.extension')
+ end
+
+ it 'expands the alias' do
+ query
+
+ command['projection'].should == {'phone_numbers.ext' => 1}
+ end
+ end
+end
diff --git a/spec/mongoid/criteria_spec.rb b/spec/mongoid/criteria_spec.rb
index 475d6a473d..fa5e5e3689 100644
--- a/spec/mongoid/criteria_spec.rb
+++ b/spec/mongoid/criteria_spec.rb
@@ -1071,6 +1071,25 @@
end
end
+ describe "#eq" do
+
+ let!(:match) do
+ Band.create(member_count: 5)
+ end
+
+ let!(:non_match) do
+ Band.create(member_count: 1)
+ end
+
+ let(:criteria) do
+ Band.eq(member_count: 5)
+ end
+
+ it "returns the matching documents" do
+ expect(criteria).to eq([ match ])
+ end
+ end
+
describe "#gt" do
let!(:match) do
diff --git a/spec/mongoid/document_spec.rb b/spec/mongoid/document_spec.rb
index f94186c0a0..c1d02afe85 100644
--- a/spec/mongoid/document_spec.rb
+++ b/spec/mongoid/document_spec.rb
@@ -430,27 +430,6 @@ class << self; attr_accessor :name; end
Person.new(title: "Sir")
end
- describe 'id' do
- context 'rails < 6' do
- max_rails_version '5.2'
-
- it 'is a BSON::ObjectId' do
- id = person.as_json['_id']
- expect(id).to be_a(BSON::ObjectId)
- end
- end
-
- context 'rails >= 6' do
- min_rails_version '6.0'
-
- it 'is a hash with $oid' do
- id = person.as_json['_id']
- expect(id).to be_a(Hash)
- expect(id['$oid']).to be_a(String)
- end
- end
- end
-
context "when no options are provided" do
it "does not apply any options" do
diff --git a/spec/mongoid/fields_spec.rb b/spec/mongoid/fields_spec.rb
index 0acfba89e7..62549de6b5 100644
--- a/spec/mongoid/fields_spec.rb
+++ b/spec/mongoid/fields_spec.rb
@@ -1397,4 +1397,116 @@ class DiscriminatorChild2 < DiscriminatorParent
end
end
end
+
+ describe '.database_field_name' do
+
+ shared_examples_for 'database_field_name' do
+ subject { Person.database_field_name(key) }
+
+ context 'non-aliased field name' do
+ let(:key) { 't' }
+ it { is_expected.to eq 't' }
+ end
+
+ context 'aliased field name' do
+ let(:key) { 'test' }
+ it { is_expected.to eq 't' }
+ end
+
+ context 'non-aliased embeds one relation' do
+ let(:key) { 'pass' }
+ it { is_expected.to eq 'pass' }
+ end
+
+ context 'aliased embeds one relation' do
+ let(:key) { 'passport' }
+ it { is_expected.to eq 'pass' }
+ end
+
+ context 'non-aliased embeds many relation' do
+ let(:key) { 'mobile_phones' }
+ it { is_expected.to eq 'mobile_phones' }
+ end
+
+ context 'aliased embeds many relation' do
+ let(:key) { 'phones' }
+ it { is_expected.to eq 'mobile_phones' }
+ end
+
+ context 'non-aliased embeds one field' do
+ let(:key) { 'pass.exp' }
+ it { is_expected.to eq 'pass.exp' }
+ end
+
+ context 'aliased embeds one field' do
+ let(:key) { 'passport.expiration_date' }
+ it { is_expected.to eq 'pass.exp' }
+ end
+
+ context 'non-aliased embeds many field' do
+ let(:key) { 'mobile_phones.landline' }
+ it { is_expected.to eq 'mobile_phones.landline' }
+ end
+
+ context 'aliased embeds many field' do
+ let(:key) { 'phones.extension' }
+ it { is_expected.to eq 'mobile_phones.ext' }
+ end
+
+ context 'aliased multi-level embedded document' do
+ let(:key) { 'phones.extension' }
+ it { is_expected.to eq 'mobile_phones.ext' }
+ end
+
+ context 'non-aliased multi-level embedded document' do
+ let(:key) { 'phones.extension' }
+ it { is_expected.to eq 'mobile_phones.ext' }
+ end
+
+ context 'aliased multi-level embedded document field' do
+ let(:key) { 'mobile_phones.country_code.code' }
+ it { is_expected.to eq 'mobile_phones.country_code.code' }
+ end
+
+ context 'non-aliased multi-level embedded document field' do
+ let(:key) { 'phones.country_code.iso_alpha2_code' }
+ it { is_expected.to eq 'mobile_phones.country_code.iso' }
+ end
+
+ context 'when field is unknown' do
+ let(:key) { 'shenanigans' }
+ it { is_expected.to eq 'shenanigans' }
+ end
+
+ context 'when embedded field is unknown' do
+ let(:key) { 'phones.bamboozle' }
+ it { is_expected.to eq 'mobile_phones.bamboozle' }
+ end
+
+ context 'when multi-level embedded field is unknown' do
+ let(:key) { 'phones.bamboozle.brouhaha' }
+ it { is_expected.to eq 'mobile_phones.bamboozle.brouhaha' }
+ end
+ end
+
+ context 'given nil' do
+ subject { Person.database_field_name(nil) }
+ it { is_expected.to eq nil }
+ end
+
+ context 'given an empty String' do
+ subject { Person.database_field_name('') }
+ it { is_expected.to eq nil }
+ end
+
+ context 'given a String' do
+ subject { Person.database_field_name(key.to_s) }
+ it_behaves_like 'database_field_name'
+ end
+
+ context 'given a Symbol' do
+ subject { Person.database_field_name(key.to_sym) }
+ it_behaves_like 'database_field_name'
+ end
+ end
end
diff --git a/spec/mongoid/query_cache_spec.rb b/spec/mongoid/query_cache_spec.rb
index 9c172397a5..494659618f 100644
--- a/spec/mongoid/query_cache_spec.rb
+++ b/spec/mongoid/query_cache_spec.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require "spec_helper"
+require 'mongoid/association/referenced/has_many_models'
describe Mongoid::QueryCache do
@@ -998,4 +999,26 @@
end
end
end
+
+ context 'after calling none? on an association' do
+ let!(:host) do
+ HmmSchool.delete_all
+ school = HmmSchool.create!
+ 5.times do
+ HmmStudent.create!(school: school)
+ end
+ end
+
+ let(:school) { HmmSchool.first }
+
+ before do
+ Mongoid::QueryCache.clear_cache
+
+ school.students.none?
+ end
+
+ it 'returns all children for the association' do
+ school.students.to_a.length.should == 5
+ end
+ end
end
diff --git a/spec/support/models/country_code.rb b/spec/support/models/country_code.rb
index 8575fbc6b9..53665f0540 100644
--- a/spec/support/models/country_code.rb
+++ b/spec/support/models/country_code.rb
@@ -6,5 +6,7 @@ class CountryCode
field :_id, type: Integer, overwrite: true, default: ->{ code }
field :code, type: Integer
+ field :iso, as: :iso_alpha2_code
+
embedded_in :phone_number, class_name: "Phone"
end
diff --git a/spec/support/models/passport.rb b/spec/support/models/passport.rb
index 8a608e40c6..91159b5f3a 100644
--- a/spec/support/models/passport.rb
+++ b/spec/support/models/passport.rb
@@ -2,7 +2,10 @@
class Passport
include Mongoid::Document
+
field :number, type: String
field :country, type: String
+ field :exp, as: :expiration_date, type: Date
+
embedded_in :person, autobuild: true
end
diff --git a/spec/support/models/phone.rb b/spec/support/models/phone.rb
index 82e4f25f23..3d8ca55dcd 100644
--- a/spec/support/models/phone.rb
+++ b/spec/support/models/phone.rb
@@ -4,9 +4,10 @@ class Phone
include Mongoid::Document
field :_id, type: String, overwrite: true, default: ->{ number }
-
field :number
+ field :ext, as: :extension
field :landline, type: Boolean
+
embeds_one :country_code
embedded_in :person
end