Skip to content

Commit

Permalink
Fix MONGOID-5033 Cannot use any_of with multiple conditions when usin…
Browse files Browse the repository at this point in the history
…g symbol operators on Time fields and passing Date instances (#4936)

* Fix MONGOID-5033 Cannot use any_of with multiple conditions when using symbol operators on Time fields and passing Date instances

* Update docs/tutorials/mongoid-queries.txt

Co-authored-by: Andreas Braun <[email protected]>

* Update docs/tutorials/mongoid-queries.txt

Co-authored-by: Andreas Braun <[email protected]>

Co-authored-by: Oleg Pudeyev <[email protected]>
Co-authored-by: Andreas Braun <[email protected]>
  • Loading branch information
3 people authored Dec 22, 2020
1 parent 07c544a commit 988386f
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 4 deletions.
25 changes: 25 additions & 0 deletions docs/tutorials/mongoid-documents.txt
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,31 @@ or else the values will not store properly.
end
end


Time Fields
-----------

``Time`` fields store values as ``Time`` instances in the :ref:`configured
time zone <time-zones>`.

``Date`` and ``DateTime`` instances are converted to ``Time`` instances upon
assignment to a ``Time`` field:

.. code-block:: ruby

class Voter
include Mongoid::Document

field :registered_at, type: Time
end

Voter.new(registered_at: Date.today)
# => #<Voter _id: 5fdd80392c97a618f07ba344, registered_at: 2020-12-18 05:00:00 UTC>

In the above example, the value was interpreted as the beginning of today in
local time, because the application was not configured to use UTC times.


Date Fields
-----------

Expand Down
57 changes: 57 additions & 0 deletions docs/tutorials/mongoid-queries.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1478,6 +1478,63 @@ It is also possible to query using PCRE syntax by constructing
# => #<Band _id: 5dc9f7d5ce4ef34893354323, name: "Sun Project", description: "Sun\nProject">


Conditions On Fields
====================

When a condition uses a field defined in the model, the value being specified
in the condition is converted according to the rules of the field, if any.
For example, consider the following model definition that contains a ``Time``
field, a ``Date`` field and an implicit ``Object`` field, and also
intentionally does not define a field called ``deregistered_at``:

.. code-block:: ruby

class Voter
include Mongoid::Document

field :born_on, type: Date
field :registered_at, type: Time
field :voted_at
end

Queries on ``born_on`` and ``registered_at`` fields using ``Date`` and ``Time``
values, respectively, are straightforward:

.. code-block:: ruby

Voter.where(born_on: Date.today).selector
# => {"born_on"=>2020-12-18 00:00:00 UTC}

Voter.where(registered_at: Time.now).selector
# => {"registered_at"=>2020-12-19 04:33:36.939788067 UTC}

But, note the differences in behavior when providing a ``Date`` instance
in all possible scenarios:

.. code-block:: ruby

Voter.where(born_on: Date.today).selector
# => {"born_on"=>2020-12-18 00:00:00 UTC}

Voter.where(registered_at: Date.today).selector
# => {"registered_at"=>2020-12-18 00:00:00 -0500}

Voter.where(voted_at: Date.today).selector
# => {"voted_at"=>Fri, 18 Dec 2020}

Voter.where(deregistered_at: Date.today).selector
# => {"deregistered_at"=>2020-12-18 00:00:00 UTC}

When using the ``registered_at`` field which is of type ``Time``, the date
was interpreted to be in local time (as per the :ref:`configured time zone
<time-zones>`). When using the ``born_on`` field which is of type ``Date``,
the date was interpreted to be in UTC. When using the ``voted_at`` field
which was defined without a type (hence implicitly as an ``Object``),
the date was used unmodified in the constructed query. When using a
nonexistent field ``deregistered_at`` the date was interpreted to be in UTC
and converted to a time, matching the behavior of querying a ``Date`` field.


Queries + Persistence
=====================

Expand Down
4 changes: 0 additions & 4 deletions lib/mongoid/criteria/queryable/selector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,6 @@ def evolve_multi(specs)
# {'foo' => {'$lt' => 5}}. This step should be done after all
# value-based processing is complete.
if key.is_a?(Key)
if serializer && evolved_value != value
raise NotImplementedError, "This method is not prepared to handle key being a Key and serializer being not nil"
end

evolved_value = key.transform_value(evolved_value)
end

Expand Down
36 changes: 36 additions & 0 deletions spec/mongoid/criteria/queryable/selectable_logical_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1517,6 +1517,42 @@
end
end
end

context 'when using multiple criteria and symbol operators' do
context 'when using fields that meaningfully evolve values' do

let(:query) do
Dictionary.any_of({a: 1}, :published.gt => Date.new(2020, 2, 3))
end

it 'generates the expected query' do
query.selector.should == {'$or' => [
{'a' => 1},
# Date instance is converted to a Time instance in local time,
# because we are querying on a Time field and dates are interpreted
# in local time when assigning to Time fields
{'published' => {'$gt' => Time.local(2020, 2, 3)}},
]}
end
end

context 'when using fields that do not meaningfully evolve values' do

let(:query) do
Dictionary.any_of({a: 1}, :submitted_on.gt => Date.new(2020, 2, 3))
end

it 'generates the expected query' do
query.selector.should == {'$or' => [
{'a' => 1},
# Date instance is converted to a Time instance in UTC,
# because we are querying on a Date field and dates are interpreted
# in UTC when persisted as dates by Mongoid
{'submitted_on' => {'$gt' => Time.utc(2020, 2, 3)}},
]}
end
end
end
end

describe "#not" do
Expand Down
6 changes: 6 additions & 0 deletions spec/support/models/dictionary.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ class Dictionary
field :name, type: String
field :publisher, type: String
field :year, type: Integer

# This field must be a Time
field :published, type: Time

# This field must be a Date
field :submitted_on, type: Date

field :description, type: String, localize: true
field :l, type: String, as: :language
has_many :words, validate: false
Expand Down

0 comments on commit 988386f

Please sign in to comment.