Skip to content

Commit b1c3718

Browse files
neilshwekyp-mongo
andauthored
MONGOID-5330 Operator methods overwrite previous conditions (#5263)
* MONGOID-5330 add failing test * MONGOID-5330 pend test * MONGOID-5330 make gt use and * MONGOID-5330 change hard coded value * MONGOID-5330 fix tests * MONGOID-5330 add feature flag * MONGOID-5330 use the feature flag * MONGOID-5330 add testing and fix eq * MONGOID-5330 change op map * MONGOID-5330 fix elemMatch * Update lib/mongoid/criteria/queryable/mergeable.rb * Update lib/mongoid/criteria/queryable/mergeable.rb * Update lib/mongoid/criteria/queryable/mergeable.rb * MONGOID-5330 make this feature flag in 7.5 * MONGOID-5330 remove extra spacing * MONGOID-5330 update docs to update default * MONGOID-5330 update tests after switching defaults * MONGOID-5330 update docs with note about strings vs symbols * MONGOID-5330 update test * MONGOID-5330 change feature flag to overwrite Co-authored-by: Oleg Pudeyev <[email protected]>
1 parent 90ec41c commit b1c3718

File tree

8 files changed

+366
-57
lines changed

8 files changed

+366
-57
lines changed

docs/reference/configuration.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,12 @@ for details on driver options.
395395
# (default: false)
396396
#object_id_as_json_oid: true
397397

398+
# When chaining the same operators that use the same field, setting this
399+
# feature flag to false will cause those operators to be combined using an
400+
# and. Setting this feature flag to true will cause the later chained
401+
# operators to overwrite the earlier ones. (default: false)
402+
#overwrite_chained_operators: false
403+
398404
# Preload all models in development, needed when models use
399405
# inheritance. (default: false)
400406
preload_models: false

docs/release-notes/mongoid-7.5.txt

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
***********
2+
Mongoid 7.5
3+
***********
4+
5+
.. default-domain:: mongodb
6+
7+
.. contents:: On this page
8+
:local:
9+
:backlinks: none
10+
:depth: 2
11+
:class: singlecol
12+
13+
This page describes significant changes and improvements in Mongoid 7.5.
14+
The complete list of releases is available `on GitHub
15+
<https://github.com/mongodb/mongoid/releases>`_ and `in JIRA
16+
<https://jira.mongodb.org/projects/MONGOID?selectedItem=com.atlassian.jira.jira-projects-plugin:release-page>`_;
17+
please consult GitHub releases for detailed release notes and JIRA for
18+
the complete list of issues fixed in each release, including bug fixes.
19+
20+
21+
``Document#to_a`` deprecated
22+
````````````````````````````
23+
24+
The ``Document#to_a`` method is deprecated in Mongoid 7.5.
25+
26+
27+
Combine Chained Operators Using ``and`` Instead of ``override``
28+
```````````````````````````````````````````````````````````````
29+
30+
Mongoid 7.5 with the ``Mongoid.overwrite_chained_operators`` option set to ``false``
31+
will combine conditions that use the same operator and field using an ``and``.
32+
For example, in the following query:
33+
34+
.. code-block:: ruby
35+
36+
Band.ne(name: "The Rolling Stones").ne(name: "The Beatles")
37+
38+
Mongoid 7.5 with the ``Mongoid.overwrite_chained_operators`` option set to ``false``
39+
will generate the following criteria:
40+
41+
.. code-block:: ruby
42+
43+
#<Mongoid::Criteria
44+
selector: {"name"=>{"$ne"=>"The Rolling Stones"}, "$and"=>[{"name"=>{"$ne"=>"The Beatles"}}]}
45+
options: {}
46+
class: Band
47+
embedded: false>
48+
49+
In Mongoid 7.4 and earlier, and in 7.5 with the ``Mongoid.overwrite_chained_operators``
50+
option set to ``true``, the following criteria would be generated instead which
51+
overwrites the first condition:
52+
53+
.. code-block:: ruby
54+
55+
#<Mongoid::Criteria
56+
selector: {"name"=>{"$ne"=>"The Beatles"}}
57+
options: {}
58+
class: Band
59+
embedded: false>
60+
61+
The following functions are affected by this change:
62+
63+
- ``eq``
64+
- ``elem_match``
65+
- ``gt``
66+
- ``gte``
67+
- ``lt``
68+
- ``lte``
69+
- ``mod``
70+
- ``ne``
71+
- ``near``
72+
- ``near_sphere``
73+
74+
.. note::
75+
76+
In Mongoid 7.5 with the ``Mongoid.overwrite_chained_operators`` option set to
77+
``false``, nested keys in the generated selector will always be strings,
78+
whereas in Mongoid 7.4 and earlier, and in 7.5 with the
79+
``Mongoid.overwrite_chained_operators`` option set to ``true``, nested keys in
80+
the selector can be strings or symbols, depending on what was passed to the
81+
operator.
82+

docs/release-notes/mongoid-8.0.txt

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Default Option Values Changed
3131
**Breaking change:** The following options have had their default values
3232
changed in Mongoid 8.0:
3333

34+
- ``:overwrite_chained_operators`` => ``false``
3435
- ``:broken_aggregables`` => ``false``
3536
- ``:broken_alias_handling`` => ``false``
3637
- ``:broken_and`` => ``false``
@@ -161,20 +162,20 @@ Mongoid 8.0 behavior:
161162

162163
.. code-block:: ruby
163164

164-
Band.any_of({name: 'Rolling Stone'}, {founded: 1990}).
165+
Band.any_of({name: 'The Rolling Stones'}, {founded: 1990}).
165166
any_of({members: 2}, {last_tour: 1995})
166167
# =>
167168
# #<Mongoid::Criteria
168-
# selector: {"$or"=>[{"name"=>"Rolling Stone"}, {"founded"=>1990}],
169+
# selector: {"$or"=>[{"name"=>"The Rolling Stones"}, {"founded"=>1990}],
169170
# "$and"=>[{"$or"=>[{"members"=>2}, {"last_tour"=>1995}]}]}
170171
# options: {}
171172
# class: Band
172173
# embedded: false>
173174

174-
Band.any_of({name: 'Rolling Stone'}, {founded: 1990}).any_of({members: 2})
175+
Band.any_of({name: 'The Rolling Stones'}, {founded: 1990}).any_of({members: 2})
175176
# =>
176177
# #<Mongoid::Criteria
177-
# selector: {"$or"=>[{"name"=>"Rolling Stone"}, {"founded"=>1990}], "members"=>2}
178+
# selector: {"$or"=>[{"name"=>"The Rolling Stones"}, {"founded"=>1990}], "members"=>2}
178179
# options: {}
179180
# class: Band
180181
# embedded: false>
@@ -183,20 +184,20 @@ Mongoid 7 behavior:
183184

184185
.. code-block:: ruby
185186

186-
Band.any_of({name: 'Rolling Stone'}, {founded: 1990}).
187+
Band.any_of({name: 'The Rolling Stones'}, {founded: 1990}).
187188
any_of({members: 2}, {last_tour: 1995})
188189
# =>
189190
# #<Mongoid::Criteria
190-
# selector: {"$or"=>[{"name"=>"Rolling Stone"}, {"founded"=>1990},
191+
# selector: {"$or"=>[{"name"=>"The Rolling Stones"}, {"founded"=>1990},
191192
# {"members"=>2}, {"last_tour"=>1995}]}
192193
# options: {}
193194
# class: Band
194195
# embedded: false>
195196

196-
Band.any_of({name: 'Rolling Stone'}, {founded: 1990}).any_of({members: 2})
197+
Band.any_of({name: 'The Rolling Stones'}, {founded: 1990}).any_of({members: 2})
197198
# =>
198199
# #<Mongoid::Criteria
199-
# selector: {"$or"=>[{"name"=>"Rolling Stone"}, {"founded"=>1990}], "members"=>2}
200+
# selector: {"$or"=>[{"name"=>"The Rolling Stones"}, {"founded"=>1990}], "members"=>2}
200201
# options: {}
201202
# class: Band
202203
# embedded: false>

lib/mongoid/config.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ module Config
115115
# demongoize the values on returning them.
116116
option :legacy_pluck_distinct, default: false
117117

118+
# Combine chained operators, which use the same field and operator,
119+
# using and's instead of overwriting them.
120+
option :overwrite_chained_operators, default: false
121+
118122
# Has Mongoid been configured? This is checking that at least a valid
119123
# client config exists.
120124
#

lib/mongoid/criteria/queryable/mergeable.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,27 @@ def reset_strategies!
5252
self
5353
end
5454

55+
# Merge criteria with operators using the and operator.
56+
#
57+
# @param [ Hash ] criterion The criterion to add to the criteria.
58+
# @param [ String ] operator The MongoDB operator.
59+
#
60+
# @return [ Criteria ] The resulting criteria.
61+
def and_with_operator(criterion, operator)
62+
crit = self
63+
if criterion
64+
criterion.each_pair do |field, value|
65+
val = prepare(field, operator, value)
66+
# The prepare method already takes the negation into account. We
67+
# set negating to false here so that ``and`` doesn't also apply
68+
# negation and we have a double negative.
69+
crit.negating = false
70+
crit = crit.and(field => val)
71+
end
72+
end
73+
crit
74+
end
75+
5576
private
5677

5778
# Adds the criterion to the existing selection.

lib/mongoid/criteria/queryable/selectable.rb

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ def elem_match(criterion)
154154
raise Errors::CriteriaArgumentRequired, :elem_match
155155
end
156156

157-
__override__(criterion, "$elemMatch")
157+
and_or_override(criterion, "$elemMatch")
158158
end
159159
key :elem_match, :override, "$elemMatch"
160160

@@ -257,7 +257,7 @@ def eq(criterion)
257257
raise Errors::CriteriaArgumentRequired, :eq
258258
end
259259

260-
__override__(criterion, "$eq")
260+
and_or_override(criterion, "$eq")
261261
end
262262
key :eq, :override, "$eq"
263263

@@ -277,7 +277,7 @@ def gt(criterion)
277277
raise Errors::CriteriaArgumentRequired, :gt
278278
end
279279

280-
__override__(criterion, "$gt")
280+
and_or_override(criterion, "$gt")
281281
end
282282
key :gt, :override, "$gt"
283283

@@ -297,7 +297,7 @@ def gte(criterion)
297297
raise Errors::CriteriaArgumentRequired, :gte
298298
end
299299

300-
__override__(criterion, "$gte")
300+
and_or_override(criterion, "$gte")
301301
end
302302
key :gte, :override, "$gte"
303303

@@ -353,7 +353,7 @@ def lt(criterion)
353353
raise Errors::CriteriaArgumentRequired, :lt
354354
end
355355

356-
__override__(criterion, "$lt")
356+
and_or_override(criterion, "$lt")
357357
end
358358
key :lt, :override, "$lt"
359359

@@ -373,7 +373,7 @@ def lte(criterion)
373373
raise Errors::CriteriaArgumentRequired, :lte
374374
end
375375

376-
__override__(criterion, "$lte")
376+
and_or_override(criterion, "$lte")
377377
end
378378
key :lte, :override, "$lte"
379379

@@ -410,7 +410,7 @@ def mod(criterion)
410410
raise Errors::CriteriaArgumentRequired, :mod
411411
end
412412

413-
__override__(criterion, "$mod")
413+
and_or_override(criterion, "$mod")
414414
end
415415
key :mod, :override, "$mod"
416416

@@ -430,7 +430,7 @@ def ne(criterion)
430430
raise Errors::CriteriaArgumentRequired, :ne
431431
end
432432

433-
__override__(criterion, "$ne")
433+
and_or_override(criterion, "$ne")
434434
end
435435
alias :excludes :ne
436436
key :ne, :override, "$ne"
@@ -451,7 +451,7 @@ def near(criterion)
451451
raise Errors::CriteriaArgumentRequired, :near
452452
end
453453

454-
__override__(criterion, "$near")
454+
and_or_override(criterion, "$near")
455455
end
456456
key :near, :override, "$near"
457457

@@ -471,7 +471,7 @@ def near_sphere(criterion)
471471
raise Errors::CriteriaArgumentRequired, :near_sphere
472472
end
473473

474-
__override__(criterion, "$nearSphere")
474+
and_or_override(criterion, "$nearSphere")
475475
end
476476
key :near_sphere, :override, "$nearSphere"
477477

@@ -902,6 +902,22 @@ def selection(criterion = nil)
902902
end
903903
end
904904

905+
# Combine operator expessions onto a Criteria using either
906+
# an override or ands depending on the status of the
907+
# Mongoid.overwrite_chained_operators feature flag.
908+
#
909+
# @param [ Hash ] The criterion to add to the criteria.
910+
# @param [ String ] operator The MongoDB operator.
911+
#
912+
# @return [ Criteria ] The resulting criteria.
913+
def and_or_override(criterion, operator)
914+
if Mongoid.overwrite_chained_operators
915+
__override__(criterion, operator)
916+
else
917+
and_with_operator(criterion, operator)
918+
end
919+
end
920+
905921
class << self
906922

907923
# Get the methods on the selectable that can be forwarded to from a model.

spec/mongoid/config_spec.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,12 @@
335335
it_behaves_like "a config option"
336336
end
337337

338+
context 'when setting the overwrite_chained_operators option in the config' do
339+
let(:option) { :overwrite_chained_operators }
340+
let(:default) { false }
341+
342+
it_behaves_like "a config option"
343+
end
338344

339345
describe "#load!" do
340346

0 commit comments

Comments
 (0)