mongoid 7.1.0 → 7.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/CHANGELOG.md +6 -6
  5. data/README.md +1 -1
  6. data/lib/config/locales/en.yml +4 -4
  7. data/lib/mongoid/association/referenced/belongs_to/eager.rb +38 -2
  8. data/lib/mongoid/association/referenced/eager.rb +29 -9
  9. data/lib/mongoid/config.rb +39 -9
  10. data/lib/mongoid/criteria.rb +16 -3
  11. data/lib/mongoid/criteria/queryable/pipeline.rb +3 -2
  12. data/lib/mongoid/criteria/queryable/selectable.rb +94 -7
  13. data/lib/mongoid/criteria/queryable/storable.rb +104 -99
  14. data/lib/mongoid/errors/eager_load.rb +2 -0
  15. data/lib/mongoid/persistable/pushable.rb +7 -1
  16. data/lib/mongoid/serializable.rb +9 -3
  17. data/lib/mongoid/version.rb +1 -1
  18. data/lib/rails/generators/mongoid/config/templates/mongoid.yml +32 -23
  19. data/spec/app/models/coding.rb +4 -0
  20. data/spec/app/models/coding/pull_request.rb +12 -0
  21. data/spec/app/models/delegating_patient.rb +16 -0
  22. data/spec/app/models/publication.rb +5 -0
  23. data/spec/app/models/publication/encyclopedia.rb +12 -0
  24. data/spec/app/models/publication/review.rb +14 -0
  25. data/spec/integration/document_spec.rb +22 -0
  26. data/spec/mongoid/association/referenced/belongs_to/eager_spec.rb +193 -10
  27. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +504 -127
  28. data/spec/mongoid/criteria/queryable/selectable_spec.rb +52 -0
  29. data/spec/mongoid/criteria/queryable/storable_spec.rb +80 -2
  30. data/spec/mongoid/criteria_spec.rb +32 -0
  31. data/spec/mongoid/persistable/pushable_spec.rb +55 -1
  32. data/spec/mongoid/serializable_spec.rb +129 -18
  33. data/spec/spec_helper.rb +2 -0
  34. data/spec/support/expectations.rb +3 -1
  35. data/spec/support/helpers.rb +11 -0
  36. metadata +504 -490
  37. metadata.gz.sig +0 -0
@@ -16,7 +16,161 @@ describe Mongoid::Criteria::Queryable::Selectable do
16
16
  end
17
17
  end
18
18
 
19
- shared_examples_for 'a non-combining logical operation' do
19
+ # Hoisting means the operator can be elided, for example
20
+ # Foo.and(a: 1) produces simply {'a' => 1}.
21
+ shared_examples_for 'a hoisting logical operation' do
22
+
23
+ let(:query) do
24
+ Mongoid::Query.new
25
+ end
26
+
27
+ context "when provided a single criterion" do
28
+
29
+ shared_examples_for 'adds the conditions to top level' do
30
+
31
+ it "adds the conditions to top level" do
32
+ expect(selection.selector).to eq(
33
+ "field" => [ 1, 2 ]
34
+ )
35
+ end
36
+
37
+ it_behaves_like 'returns a cloned query'
38
+ end
39
+
40
+ let(:selection) do
41
+ query.send(tested_method, field: [ 1, 2 ])
42
+ end
43
+
44
+ it_behaves_like 'adds the conditions to top level'
45
+
46
+ context 'when the criterion is wrapped in an array' do
47
+ let(:selection) do
48
+ query.send(tested_method, [{field: [ 1, 2 ] }])
49
+ end
50
+
51
+ it_behaves_like 'adds the conditions to top level'
52
+ end
53
+
54
+ context 'when the criterion is wrapped in a deep array with nil elements' do
55
+ let(:selection) do
56
+ query.send(tested_method, [[[{field: [ 1, 2 ] }]], [nil]])
57
+ end
58
+
59
+ it_behaves_like 'adds the conditions to top level'
60
+ end
61
+ end
62
+
63
+ context 'when argument is a Criteria' do
64
+ let(:base) do
65
+ query.where(hello: 'world')
66
+ end
67
+
68
+ let(:other) do
69
+ query.where(foo: 'bar')
70
+ end
71
+
72
+ let(:result) { base.send(tested_method, other) }
73
+
74
+ it 'combines' do
75
+ expect(result.selector).to eq(
76
+ 'hello' => 'world',
77
+ 'foo' => 'bar',
78
+ )
79
+ end
80
+ end
81
+
82
+ context "when provided a single criterion that is handled via Key" do
83
+
84
+ shared_examples_for 'adds the conditions to top level' do
85
+
86
+ it "adds the conditions to top level" do
87
+ expect(selection.selector).to eq({
88
+ "field" => {'$gt' => 3 },
89
+ })
90
+ end
91
+
92
+ it_behaves_like 'returns a cloned query'
93
+ end
94
+
95
+ let(:selection) do
96
+ query.send(tested_method, :field.gt => 3)
97
+ end
98
+
99
+ it_behaves_like 'adds the conditions to top level'
100
+
101
+ context 'when the criterion is wrapped in an array' do
102
+ let(:selection) do
103
+ query.send(tested_method, [{ :field.gt => 3 }])
104
+ end
105
+
106
+ it_behaves_like 'adds the conditions to top level'
107
+ end
108
+
109
+ context 'when the criterion is wrapped in a deep array with nil elements' do
110
+ let(:selection) do
111
+ query.send(tested_method, [[[{ :field.gt => 3 }]], [nil]])
112
+ end
113
+
114
+ it_behaves_like 'adds the conditions to top level'
115
+ end
116
+ end
117
+
118
+ context "when provided a nested criterion" do
119
+
120
+ let(:selection) do
121
+ query.send(tested_method, :test.elem_match => { :field.in => [ 1, 2 ] })
122
+ end
123
+
124
+ it "builds the correct selector" do
125
+ expect(selection.selector).to eq({
126
+ "test" => { "$elemMatch" => { "field" => { "$in" => [ 1, 2 ] }}}
127
+ })
128
+ end
129
+
130
+ it_behaves_like 'returns a cloned query'
131
+ end
132
+
133
+ context "when chaining the criteria" do
134
+
135
+ context "when the criteria are for different fields" do
136
+
137
+ let(:selection) do
138
+ query.and(first: [ 1, 2 ]).send(tested_method, second: [ 3, 4 ])
139
+ end
140
+
141
+ it "adds the conditions to top level" do
142
+ expect(selection.selector).to eq({
143
+ "first" => [ 1, 2 ],
144
+ "second" => [ 3, 4 ],
145
+ })
146
+ end
147
+
148
+ it_behaves_like 'returns a cloned query'
149
+ end
150
+
151
+ context "when the criteria are on the same field" do
152
+
153
+ let(:selection) do
154
+ query.and(first: [ 1, 2 ]).send(tested_method, first: [ 3, 4 ])
155
+ end
156
+
157
+ it "combines via $and operator" do
158
+ expect(selection.selector).to eq({
159
+ "first" => [ 1, 2 ],
160
+ "$and" => [
161
+ { "first" => [ 3, 4 ] }
162
+ ]
163
+ })
164
+ end
165
+
166
+ it_behaves_like 'returns a cloned query'
167
+ end
168
+ end
169
+ end
170
+
171
+ # Non-hoisting means the operator is always present, for example
172
+ # Foo.or(a: 1) produces {'$or' => [{'a' => 1}]}.
173
+ shared_examples_for 'a non-hoisting logical operation' do
20
174
 
21
175
  context 'when there is a single predicate' do
22
176
  let(:query) do
@@ -88,6 +242,11 @@ describe Mongoid::Criteria::Queryable::Selectable do
88
242
 
89
243
  describe "#and" do
90
244
 
245
+ let(:tested_method) { :and }
246
+ let(:expected_operator) { '$and' }
247
+
248
+ it_behaves_like 'a hoisting logical operation'
249
+
91
250
  context "when provided no criterion" do
92
251
 
93
252
  let(:selection) do
@@ -122,93 +281,6 @@ describe Mongoid::Criteria::Queryable::Selectable do
122
281
  it_behaves_like 'returns a cloned query'
123
282
  end
124
283
 
125
- context "when provided a single criterion" do
126
-
127
- shared_examples_for 'adds the conditions to top level' do
128
-
129
- it "adds the conditions to top level" do
130
- expect(selection.selector).to eq({
131
- "field" => [ 1, 2 ]
132
- })
133
- end
134
-
135
- it_behaves_like 'returns a cloned query'
136
- end
137
-
138
- let(:selection) do
139
- query.and(field: [ 1, 2 ])
140
- end
141
-
142
- it_behaves_like 'adds the conditions to top level'
143
-
144
- context 'when the criterion is wrapped in an array' do
145
- let(:selection) do
146
- query.and([{field: [ 1, 2 ] }])
147
- end
148
-
149
- it_behaves_like 'adds the conditions to top level'
150
- end
151
-
152
- context 'when the criterion is wrapped in a deep array with nil elements' do
153
- let(:selection) do
154
- query.and([[[{field: [ 1, 2 ] }]], [nil]])
155
- end
156
-
157
- it_behaves_like 'adds the conditions to top level'
158
- end
159
- end
160
-
161
- context "when provided a single criterion that is handled via Key" do
162
-
163
- shared_examples_for 'adds the conditions to top level' do
164
-
165
- it "adds the conditions to top level" do
166
- expect(selection.selector).to eq({
167
- "field" => {'$gt' => 3 },
168
- })
169
- end
170
-
171
- it_behaves_like 'returns a cloned query'
172
- end
173
-
174
- let(:selection) do
175
- query.and(:field.gt => 3)
176
- end
177
-
178
- it_behaves_like 'adds the conditions to top level'
179
-
180
- context 'when the criterion is wrapped in an array' do
181
- let(:selection) do
182
- query.and([{ :field.gt => 3 }])
183
- end
184
-
185
- it_behaves_like 'adds the conditions to top level'
186
- end
187
-
188
- context 'when the criterion is wrapped in a deep array with nil elements' do
189
- let(:selection) do
190
- query.and([[[{ :field.gt => 3 }]], [nil]])
191
- end
192
-
193
- it_behaves_like 'adds the conditions to top level'
194
- end
195
- end
196
-
197
- context "when provided a nested criterion" do
198
-
199
- let(:selection) do
200
- query.and(:test.elem_match => { :field.in => [ 1, 2 ] })
201
- end
202
-
203
- it "builds the correct selector" do
204
- expect(selection.selector).to eq({
205
- "test" => { "$elemMatch" => { "field" => { "$in" => [ 1, 2 ] }}}
206
- })
207
- end
208
-
209
- it_behaves_like 'returns a cloned query'
210
- end
211
-
212
284
  context "when provided multiple criteria" do
213
285
 
214
286
  context "when the criteria is already included" do
@@ -264,43 +336,6 @@ describe Mongoid::Criteria::Queryable::Selectable do
264
336
  end
265
337
  end
266
338
 
267
- context "when chaining the criteria" do
268
-
269
- context "when the criteria are for different fields" do
270
-
271
- let(:selection) do
272
- query.and(first: [ 1, 2 ]).and(second: [ 3, 4 ])
273
- end
274
-
275
- it "adds the conditions to top level" do
276
- expect(selection.selector).to eq({
277
- "first" => [ 1, 2 ],
278
- "second" => [ 3, 4 ],
279
- })
280
- end
281
-
282
- it_behaves_like 'returns a cloned query'
283
- end
284
-
285
- context "when the criteria are on the same field" do
286
-
287
- let(:selection) do
288
- query.and(first: [ 1, 2 ]).and(first: [ 3, 4 ])
289
- end
290
-
291
- it "combines via $and operator" do
292
- expect(selection.selector).to eq({
293
- "first" => [ 1, 2 ],
294
- "$and" => [
295
- { "first" => [ 3, 4 ] }
296
- ]
297
- })
298
- end
299
-
300
- it_behaves_like 'returns a cloned query'
301
- end
302
- end
303
-
304
339
  context 'when argument is a Criteria' do
305
340
  let(:query) do
306
341
  Mongoid::Query.new.where(hello: 'world')
@@ -424,6 +459,54 @@ describe Mongoid::Criteria::Queryable::Selectable do
424
459
  it_behaves_like 'adds most recent criterion as $and'
425
460
  end
426
461
  end
462
+
463
+ context 'when conditions already exist in criteria' do
464
+ let(:base_selection) do
465
+ query.where(foo: 'bar')
466
+ end
467
+
468
+ context 'when hash conditions are given' do
469
+ let(:selection) do
470
+ base_selection.and(hello: 'world')
471
+ end
472
+
473
+ it 'adds new conditions to top level' do
474
+ selection.selector.should == {
475
+ 'foo' => 'bar',
476
+ 'hello' => 'world',
477
+ }
478
+ end
479
+ end
480
+
481
+ context 'when criteria conditions are given' do
482
+ let(:selection) do
483
+ base_selection.and(query.where(hello: 'world'))
484
+ end
485
+
486
+ it 'adds new conditions to top level' do
487
+ selection.selector.should == {
488
+ 'foo' => 'bar',
489
+ 'hello' => 'world',
490
+ }
491
+ end
492
+ end
493
+
494
+ context 'when complex criteria conditions are given' do
495
+ let(:selection) do
496
+ base_selection.and(query.or([one: 'one'], [two: 'two']))
497
+ end
498
+
499
+ it 'adds new conditions to top level' do
500
+ selection.selector.should == {
501
+ 'foo' => 'bar',
502
+ '$or' => [
503
+ {'one' => 'one'},
504
+ {'two' => 'two'},
505
+ ],
506
+ }
507
+ end
508
+ end
509
+ end
427
510
  end
428
511
 
429
512
  describe "#or" do
@@ -431,7 +514,7 @@ describe Mongoid::Criteria::Queryable::Selectable do
431
514
  let(:tested_method) { :or }
432
515
  let(:expected_operator) { '$or' }
433
516
 
434
- it_behaves_like 'a non-combining logical operation'
517
+ it_behaves_like 'a non-hoisting logical operation'
435
518
 
436
519
  context "when provided no arguments" do
437
520
 
@@ -679,7 +762,7 @@ describe Mongoid::Criteria::Queryable::Selectable do
679
762
  let(:tested_method) { :nor }
680
763
  let(:expected_operator) { '$nor' }
681
764
 
682
- it_behaves_like 'a non-combining logical operation'
765
+ it_behaves_like 'a non-hoisting logical operation'
683
766
 
684
767
  context "when provided no criterion" do
685
768
 
@@ -809,6 +892,300 @@ describe Mongoid::Criteria::Queryable::Selectable do
809
892
  end
810
893
  end
811
894
 
895
+ describe "#any_of" do
896
+
897
+ let(:tested_method) { :any_of }
898
+ let(:expected_operator) { '$or' }
899
+
900
+ it_behaves_like 'a hoisting logical operation'
901
+
902
+ # When multiple arguments are given to any_of, it behaves differently
903
+ # from and.
904
+ context 'when argument is a mix of Criteria and hashes' do
905
+ let(:query) do
906
+ Mongoid::Query.new.where(hello: 'world')
907
+ end
908
+
909
+ let(:other1) do
910
+ Mongoid::Query.new.where(foo: 'bar')
911
+ end
912
+
913
+ let(:other2) do
914
+ {bar: 42}
915
+ end
916
+
917
+ let(:other3) do
918
+ Mongoid::Query.new.where(a: 2)
919
+ end
920
+
921
+ let(:result) { query.send(tested_method, other1, other2, other3) }
922
+
923
+ it 'combines' do
924
+ expect(result.selector).to eq(
925
+ 'hello' => 'world',
926
+ expected_operator => [
927
+ {'foo' => 'bar'},
928
+ {'bar' => 42},
929
+ {'a' => 2},
930
+ ],
931
+ )
932
+ end
933
+ end
934
+
935
+ context "when provided no arguments" do
936
+
937
+ let(:selection) do
938
+ query.any_of
939
+ end
940
+
941
+ it_behaves_like 'returns a cloned query'
942
+
943
+ it "does not add any criteria" do
944
+ expect(selection.selector).to eq({})
945
+ end
946
+
947
+ it "returns the query" do
948
+ expect(selection).to eq(query)
949
+ end
950
+ end
951
+
952
+ context "when provided nil" do
953
+
954
+ let(:selection) do
955
+ query.any_of(nil)
956
+ end
957
+
958
+ it_behaves_like 'returns a cloned query'
959
+
960
+ it "does not add any criteria" do
961
+ expect(selection.selector).to eq({})
962
+ end
963
+
964
+ it "returns the query" do
965
+ expect(selection).to eq(query)
966
+ end
967
+ end
968
+
969
+ context "when provided a single criterion" do
970
+
971
+ let(:selection) do
972
+ query.any_of(field: [ 1, 2 ])
973
+ end
974
+
975
+ it_behaves_like 'returns a cloned query'
976
+
977
+ it "adds the $or selector" do
978
+ expect(selection.selector).to eq(
979
+ "field" => [ 1, 2 ],
980
+ )
981
+ end
982
+
983
+ context 'when the criterion is wrapped in array' do
984
+
985
+ let(:selection) do
986
+ query.any_of([{ field: [ 1, 2 ] }])
987
+ end
988
+
989
+ it_behaves_like 'returns a cloned query'
990
+
991
+ it "adds the condition" do
992
+ expect(selection.selector).to eq(
993
+ "field" => [ 1, 2 ],
994
+ )
995
+ end
996
+
997
+ context 'when the array has nil as one of the elements' do
998
+
999
+ let(:selection) do
1000
+ query.any_of([{ field: [ 1, 2 ] }, nil])
1001
+ end
1002
+
1003
+ it_behaves_like 'returns a cloned query'
1004
+
1005
+ it "adds the $or selector ignoring the nil element" do
1006
+ expect(selection.selector).to eq(
1007
+ "field" => [ 1, 2 ],
1008
+ )
1009
+ end
1010
+ end
1011
+ end
1012
+
1013
+ context 'when query already has a condition on another field' do
1014
+
1015
+ context 'when there is one argument' do
1016
+
1017
+ let(:selection) do
1018
+ query.where(foo: 'bar').any_of(field: [ 1, 2 ])
1019
+ end
1020
+
1021
+ it 'adds the new condition' do
1022
+ expect(selection.selector).to eq(
1023
+ 'foo' => 'bar',
1024
+ 'field' => [1, 2],
1025
+ )
1026
+ end
1027
+ end
1028
+
1029
+ context 'when there are multiple arguments' do
1030
+
1031
+ let(:selection) do
1032
+ query.where(foo: 'bar').any_of({field: [ 1, 2 ]}, {hello: 'world'})
1033
+ end
1034
+
1035
+ it 'adds the new condition' do
1036
+ expect(selection.selector).to eq(
1037
+ 'foo' => 'bar',
1038
+ '$or' => [
1039
+ {'field' => [1, 2]},
1040
+ {'hello' => 'world'},
1041
+ ],
1042
+ )
1043
+ end
1044
+ end
1045
+ end
1046
+
1047
+ context 'when query already has an $or condition and another condition' do
1048
+
1049
+ let(:selection) do
1050
+ query.or(field: [ 1, 2 ]).where(foo: 'bar').any_of(test: 1)
1051
+ end
1052
+
1053
+ it 'adds the new condition' do
1054
+ expect(selection.selector).to eq(
1055
+ '$or' => [{'field' => [1, 2]}],
1056
+ 'foo' => 'bar',
1057
+ 'test' => 1,
1058
+ )
1059
+ end
1060
+ end
1061
+ end
1062
+
1063
+ context "when provided multiple criteria" do
1064
+
1065
+ context "when the criteria are for different fields" do
1066
+
1067
+ let(:selection) do
1068
+ query.any_of({ first: [ 1, 2 ] }, { second: [ 3, 4 ] })
1069
+ end
1070
+
1071
+ it_behaves_like 'returns a cloned query'
1072
+
1073
+ it "adds the $or selector" do
1074
+ expect(selection.selector).to eq({
1075
+ "$or" => [
1076
+ { "first" => [ 1, 2 ] },
1077
+ { "second" => [ 3, 4 ] }
1078
+ ]
1079
+ })
1080
+ end
1081
+ end
1082
+
1083
+ context "when the criteria uses a Key instance" do
1084
+
1085
+ let(:selection) do
1086
+ query.any_of({ first: [ 1, 2 ] }, { :second.gt => 3 })
1087
+ end
1088
+
1089
+ it "adds the $or selector" do
1090
+ expect(selection.selector).to eq({
1091
+ "$or" => [
1092
+ { "first" => [ 1, 2 ] },
1093
+ { "second" => { "$gt" => 3 }}
1094
+ ]
1095
+ })
1096
+ end
1097
+
1098
+ it_behaves_like 'returns a cloned query'
1099
+ end
1100
+
1101
+ context "when a criterion has an aliased field" do
1102
+
1103
+ let(:selection) do
1104
+ query.any_of({ id: 1 })
1105
+ end
1106
+
1107
+ it "adds the $or selector and aliases the field" do
1108
+ expect(selection.selector).to eq(
1109
+ "_id" => 1,
1110
+ )
1111
+ end
1112
+
1113
+ it_behaves_like 'returns a cloned query'
1114
+ end
1115
+
1116
+ context "when a criterion is wrapped in an array" do
1117
+
1118
+ let(:selection) do
1119
+ query.any_of([{ first: [ 1, 2 ] }, { :second.gt => 3 }])
1120
+ end
1121
+
1122
+ it_behaves_like 'returns a cloned query'
1123
+
1124
+ it "adds the $or selector" do
1125
+ expect(selection.selector).to eq({
1126
+ "$or" => [
1127
+ { "first" => [ 1, 2 ] },
1128
+ { "second" => { "$gt" => 3 }}
1129
+ ]
1130
+ })
1131
+ end
1132
+ end
1133
+
1134
+ context "when the criteria are on the same field" do
1135
+
1136
+ let(:selection) do
1137
+ query.any_of({ first: [ 1, 2 ] }, { first: [ 3, 4 ] })
1138
+ end
1139
+
1140
+ it_behaves_like 'returns a cloned query'
1141
+
1142
+ it "appends both $or expressions" do
1143
+ expect(selection.selector).to eq({
1144
+ "$or" => [
1145
+ { "first" => [ 1, 2 ] },
1146
+ { "first" => [ 3, 4 ] }
1147
+ ]
1148
+ })
1149
+ end
1150
+ end
1151
+ end
1152
+
1153
+ context "when chaining the criteria" do
1154
+
1155
+ context "when the criteria are for different fields" do
1156
+
1157
+ let(:selection) do
1158
+ query.any_of(first: [ 1, 2 ]).any_of(second: [ 3, 4 ])
1159
+ end
1160
+
1161
+ it_behaves_like 'returns a cloned query'
1162
+
1163
+ it "adds the conditions separately" do
1164
+ expect(selection.selector).to eq(
1165
+ "first" => [ 1, 2 ],
1166
+ "second" => [ 3, 4 ],
1167
+ )
1168
+ end
1169
+ end
1170
+
1171
+ context "when the criteria are on the same field" do
1172
+
1173
+ let(:selection) do
1174
+ query.any_of(first: [ 1, 2 ]).any_of(first: [ 3, 4 ])
1175
+ end
1176
+
1177
+ it_behaves_like 'returns a cloned query'
1178
+
1179
+ it "adds the conditions separately" do
1180
+ expect(selection.selector).to eq(
1181
+ "first" => [ 1, 2 ],
1182
+ '$and' => [{"first" => [ 3, 4 ]}],
1183
+ )
1184
+ end
1185
+ end
1186
+ end
1187
+ end
1188
+
812
1189
  describe "#not" do
813
1190
 
814
1191
  context "when provided no criterion" do