mongoid 7.1.0 → 7.1.1

Sign up to get free protection for your applications and to get access to all the features.
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