mongoid 7.0.4 → 7.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/LICENSE +1 -0
  5. data/README.md +3 -2
  6. data/Rakefile +26 -0
  7. data/lib/mongoid.rb +2 -1
  8. data/lib/mongoid/association/embedded/embeds_many.rb +2 -1
  9. data/lib/mongoid/association/embedded/embeds_one.rb +2 -1
  10. data/lib/mongoid/association/proxy.rb +1 -1
  11. data/lib/mongoid/atomic.rb +13 -3
  12. data/lib/mongoid/atomic/paths/embedded.rb +1 -1
  13. data/lib/mongoid/attributes.rb +28 -20
  14. data/lib/mongoid/attributes/dynamic.rb +15 -14
  15. data/lib/mongoid/clients/sessions.rb +20 -4
  16. data/lib/mongoid/config/environment.rb +21 -8
  17. data/lib/mongoid/criteria.rb +7 -1
  18. data/lib/mongoid/criteria/modifiable.rb +13 -2
  19. data/lib/mongoid/criteria/queryable/extensions/numeric.rb +1 -1
  20. data/lib/mongoid/criteria/queryable/extensions/regexp.rb +3 -3
  21. data/lib/mongoid/criteria/queryable/extensions/time.rb +1 -1
  22. data/lib/mongoid/criteria/queryable/extensions/time_with_zone.rb +12 -0
  23. data/lib/mongoid/criteria/queryable/key.rb +67 -8
  24. data/lib/mongoid/criteria/queryable/mergeable.rb +5 -4
  25. data/lib/mongoid/criteria/queryable/selectable.rb +3 -4
  26. data/lib/mongoid/criteria/queryable/selector.rb +9 -31
  27. data/lib/mongoid/extensions/hash.rb +4 -2
  28. data/lib/mongoid/extensions/regexp.rb +1 -1
  29. data/lib/mongoid/extensions/string.rb +2 -2
  30. data/lib/mongoid/fields.rb +2 -1
  31. data/lib/mongoid/matchable.rb +14 -15
  32. data/lib/mongoid/matchable/all.rb +4 -3
  33. data/lib/mongoid/matchable/default.rb +71 -24
  34. data/lib/mongoid/matchable/regexp.rb +2 -2
  35. data/lib/mongoid/persistable/pushable.rb +11 -2
  36. data/lib/mongoid/persistence_context.rb +6 -6
  37. data/lib/mongoid/positional.rb +1 -1
  38. data/lib/mongoid/query_cache.rb +24 -11
  39. data/lib/mongoid/validatable/macros.rb +1 -1
  40. data/lib/mongoid/validatable/uniqueness.rb +1 -1
  41. data/lib/mongoid/version.rb +2 -1
  42. data/lib/rails/generators/mongoid/model/templates/model.rb.tt +1 -1
  43. data/spec/README.md +18 -0
  44. data/spec/app/models/delegating_patient.rb +16 -0
  45. data/spec/integration/app_spec.rb +192 -0
  46. data/spec/integration/associations/embedded_spec.rb +62 -0
  47. data/spec/integration/criteria/date_field_spec.rb +41 -0
  48. data/spec/integration/criteria/time_with_zone_spec.rb +32 -0
  49. data/spec/integration/document_spec.rb +22 -0
  50. data/spec/integration/matchable_spec.rb +680 -0
  51. data/spec/lite_spec_helper.rb +15 -5
  52. data/spec/mongoid/association/embedded/embeds_many_models.rb +53 -0
  53. data/spec/mongoid/association/embedded/embeds_many_spec.rb +10 -0
  54. data/spec/mongoid/association/embedded/embeds_one_spec.rb +0 -2
  55. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +140 -1
  56. data/spec/mongoid/association/referenced/has_many/enumerable_spec.rb +105 -0
  57. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +2 -1
  58. data/spec/mongoid/attributes/dynamic_spec.rb +153 -0
  59. data/spec/mongoid/attributes_spec.rb +19 -7
  60. data/spec/mongoid/clients/factory_spec.rb +2 -2
  61. data/spec/mongoid/clients/options_spec.rb +4 -4
  62. data/spec/mongoid/clients/sessions_spec.rb +20 -7
  63. data/spec/mongoid/clients/transactions_spec.rb +36 -15
  64. data/spec/mongoid/clients_spec.rb +2 -2
  65. data/spec/mongoid/contextual/atomic_spec.rb +20 -10
  66. data/spec/mongoid/contextual/geo_near_spec.rb +12 -2
  67. data/spec/mongoid/contextual/map_reduce_spec.rb +20 -5
  68. data/spec/mongoid/contextual/mongo_spec.rb +76 -53
  69. data/spec/mongoid/criteria/modifiable_spec.rb +59 -10
  70. data/spec/mongoid/criteria/queryable/extensions/numeric_spec.rb +54 -0
  71. data/spec/mongoid/criteria/queryable/extensions/regexp_spec.rb +7 -7
  72. data/spec/mongoid/criteria/queryable/extensions/string_spec.rb +1 -1
  73. data/spec/mongoid/criteria/queryable/extensions/time_spec.rb +19 -7
  74. data/spec/mongoid/criteria/queryable/extensions/time_with_zone_spec.rb +28 -1
  75. data/spec/mongoid/criteria/queryable/key_spec.rb +48 -6
  76. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +762 -0
  77. data/spec/mongoid/criteria/queryable/selectable_spec.rb +5 -224
  78. data/spec/mongoid/criteria/queryable/selector_spec.rb +37 -0
  79. data/spec/mongoid/criteria_spec.rb +7 -2
  80. data/spec/mongoid/document_fields_spec.rb +88 -0
  81. data/spec/mongoid/document_persistence_context_spec.rb +33 -0
  82. data/spec/mongoid/indexable_spec.rb +6 -4
  83. data/spec/mongoid/matchable/default_spec.rb +10 -3
  84. data/spec/mongoid/matchable/regexp_spec.rb +2 -2
  85. data/spec/mongoid/matchable_spec.rb +2 -2
  86. data/spec/mongoid/persistable/pushable_spec.rb +55 -1
  87. data/spec/mongoid/query_cache_spec.rb +62 -8
  88. data/spec/mongoid/relations/proxy_spec.rb +1 -1
  89. data/spec/mongoid/scopable_spec.rb +2 -1
  90. data/spec/mongoid/tasks/database_rake_spec.rb +13 -13
  91. data/spec/mongoid/tasks/database_spec.rb +1 -1
  92. data/spec/mongoid/validatable/uniqueness_spec.rb +33 -6
  93. data/spec/spec_helper.rb +4 -37
  94. data/spec/support/child_process_helper.rb +76 -0
  95. data/spec/support/cluster_config.rb +158 -0
  96. data/spec/support/constraints.rb +201 -30
  97. data/spec/support/expectations.rb +17 -3
  98. data/spec/support/spec_config.rb +12 -4
  99. metadata +490 -454
  100. metadata.gz.sig +0 -0
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ require 'spec_helper'
5
+ require_relative '../../mongoid/association/embedded/embeds_many_models'
6
+ require_relative '../../mongoid/association/embedded/embeds_one_models'
7
+
8
+ describe 'embedded associations' do
9
+
10
+ describe 'parent association' do
11
+ let(:parent) do
12
+ parent_cls.new
13
+ end
14
+
15
+ context 'embeds_one' do
16
+
17
+ shared_examples 'is set' do
18
+ it 'is set' do
19
+ parent.child = child_cls.new
20
+ parent.child.parent.should == parent
21
+ end
22
+ end
23
+
24
+ context 'class_name set without leading ::' do
25
+ let(:parent_cls) { EomParent }
26
+ let(:child_cls) { EomChild }
27
+
28
+ it_behaves_like 'is set'
29
+ end
30
+
31
+ context 'class_name set with leading ::' do
32
+ let(:parent_cls) { EomCcParent }
33
+ let(:child_cls) { EomCcChild }
34
+
35
+ it_behaves_like 'is set'
36
+ end
37
+ end
38
+
39
+ context 'embeds_many' do
40
+
41
+ let(:child) { parent.legislators.new }
42
+
43
+ shared_examples 'is set' do
44
+ it 'is set' do
45
+ child.congress.should == parent
46
+ end
47
+ end
48
+
49
+ context 'class_name set without leading ::' do
50
+ let(:parent_cls) { EmmCongress }
51
+
52
+ it_behaves_like 'is set'
53
+ end
54
+
55
+ context 'class_name set with leading ::' do
56
+ let(:parent_cls) { EmmCcCongress }
57
+
58
+ it_behaves_like 'is set'
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ require 'spec_helper'
5
+
6
+ describe 'Queries on Date fields' do
7
+ let(:query) do
8
+ Band.where(founded: arg)
9
+ end
10
+
11
+ let(:selector) { query.selector }
12
+
13
+ shared_examples 'converts to beginning of day in UTC' do
14
+ it 'converts to beginning of day in UTC' do
15
+ selector['founded'].should == arg.dup.beginning_of_day.utc.beginning_of_day
16
+ end
17
+ end
18
+
19
+ context 'using Time' do
20
+ let(:arg) do
21
+ Time.now.freeze
22
+ end
23
+
24
+ it_behaves_like 'converts to beginning of day in UTC'
25
+ end
26
+
27
+ context 'using TimeWithZone' do
28
+ let(:time_zone_name) { 'Pacific Time (US & Canada)' }
29
+ let(:arg) { Time.now.in_time_zone(time_zone_name).freeze }
30
+
31
+ it_behaves_like 'converts to beginning of day in UTC'
32
+ end
33
+
34
+ context 'using DateTime' do
35
+ let(:arg) do
36
+ DateTime.now.freeze
37
+ end
38
+
39
+ it_behaves_like 'converts to beginning of day in UTC'
40
+ end
41
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ require 'spec_helper'
5
+
6
+ describe 'TimeWithZone in queries' do
7
+ context 'in a non-UTC time zone' do
8
+ let(:time_zone_name) { 'Pacific Time (US & Canada)' }
9
+
10
+ before do
11
+ time = Time.now
12
+ expect(time.utc_offset).not_to eq(time.in_time_zone(time_zone_name).utc_offset)
13
+ end
14
+
15
+ let(:time_in_zone) { Time.now.in_time_zone(time_zone_name) }
16
+
17
+ let(:view_lt) do
18
+ Agency.collection.find(updated_at: {'$lt' => time_in_zone + 10.minutes})
19
+ end
20
+
21
+ let(:view_gt) do
22
+ Agency.collection.find(updated_at: {'$gt' => time_in_zone - 10.minutes})
23
+ end
24
+
25
+ let!(:agency) { Agency.create!.reload }
26
+
27
+ it 'finds the document' do
28
+ view_lt.to_a.should == [agency.attributes]
29
+ view_gt.to_a.should == [agency.attributes]
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ require 'spec_helper'
5
+
6
+ describe Mongoid::Document do
7
+ context 'when including class uses delegate' do
8
+ let(:patient) do
9
+ DelegatingPatient.new(
10
+ email: Email.new(address: 'test@example.com'),
11
+ )
12
+ end
13
+
14
+ it 'works for instance level delegation' do
15
+ patient.address.should == 'test@example.com'
16
+ end
17
+
18
+ it 'works for class level delegation' do
19
+ DelegatingPatient.default_client.should be Mongoid.default_client
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,680 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ require 'spec_helper'
5
+
6
+ describe 'Matcher' do
7
+ context 'when attribute is a scalar' do
8
+ describe 'exact match' do
9
+
10
+ let!(:slave) do
11
+ Slave.create!(address_numbers: [
12
+ AddressNumber.new(number: '123'),
13
+ AddressNumber.new(number: '456'),
14
+ ])
15
+ end
16
+
17
+ describe 'MongoDB query' do
18
+ let(:found_slave) do
19
+ Slave.where('address_numbers.number' => '123').first
20
+ end
21
+
22
+ it 'finds' do
23
+ expect(found_slave).to eq(slave)
24
+ end
25
+ end
26
+
27
+ describe 'Mongoid matcher' do
28
+ let(:found_number) do
29
+ slave.address_numbers.where(number: '123').first
30
+ end
31
+
32
+ it 'finds' do
33
+ expect(found_number).to be slave.address_numbers.first
34
+ end
35
+ end
36
+ end
37
+
38
+ describe 'regexp match on string' do
39
+
40
+ let!(:slave) do
41
+ Slave.create!(address_numbers: [
42
+ AddressNumber.new(number: '123'),
43
+ AddressNumber.new(number: '456'),
44
+ ])
45
+ end
46
+
47
+ describe 'MongoDB query' do
48
+ let(:found_slave) do
49
+ Slave.where('address_numbers.number' => /123/).first
50
+ end
51
+
52
+ it 'finds' do
53
+ expect(found_slave).to eq(slave)
54
+ end
55
+ end
56
+
57
+ describe 'Mongoid matcher' do
58
+ let(:found_number) do
59
+ slave.address_numbers.where(number: /123/).first
60
+ end
61
+
62
+ it 'finds' do
63
+ expect(found_number).to be slave.address_numbers.first
64
+ end
65
+ end
66
+ end
67
+
68
+ describe 'range match on number' do
69
+
70
+ let!(:circuit) do
71
+ Circuit.create!(buses: [
72
+ Bus.new(number: '10'),
73
+ Bus.new(number: '30'),
74
+ ])
75
+ end
76
+
77
+ describe 'MongoDB query' do
78
+ let(:found_circuit) do
79
+ Circuit.where('buses.number' => 10..15).first
80
+ end
81
+
82
+ it 'finds' do
83
+ expect(found_circuit).to eq(circuit)
84
+ end
85
+ end
86
+
87
+ describe 'Mongoid matcher' do
88
+ let(:found_bus) do
89
+ circuit.buses.where(number: 10..15).first
90
+ end
91
+
92
+ it 'finds' do
93
+ expect(found_bus).to be circuit.buses.first
94
+ end
95
+ end
96
+ end
97
+
98
+ shared_examples_for 'a field operator' do |_operator|
99
+ shared_examples_for 'behaves as expected' do
100
+ context 'matching condition' do
101
+ it 'finds' do
102
+ expect(actual_object_matching_condition).to be expected_object_matching_condition
103
+ end
104
+ end
105
+
106
+ context 'not matching condition' do
107
+ it 'does not find' do
108
+ expect(actual_object_not_matching_condition).to be nil
109
+ end
110
+ end
111
+ end
112
+
113
+ context 'as string' do
114
+ let(:operator) { _operator.to_s }
115
+
116
+ it_behaves_like 'behaves as expected'
117
+ end
118
+
119
+ context 'as symbol' do
120
+ let(:operator) { _operator.to_sym }
121
+
122
+ it_behaves_like 'behaves as expected'
123
+ end
124
+ end
125
+
126
+ describe '$eq' do
127
+
128
+ let!(:circuit) do
129
+ Circuit.new(buses: [
130
+ Bus.new(number: '10'),
131
+ Bus.new(number: '30'),
132
+ ])
133
+ end
134
+
135
+ let(:actual_object_matching_condition) do
136
+ circuit.buses.where(number: {operator => 10}).first
137
+ end
138
+
139
+ let(:expected_object_matching_condition) do
140
+ circuit.buses.first
141
+ end
142
+
143
+ let(:actual_object_not_matching_condition) do
144
+ circuit.buses.where(number: {operator => 20}).first
145
+ end
146
+
147
+ it_behaves_like 'a field operator', '$eq'
148
+ end
149
+
150
+ describe '$ne' do
151
+
152
+ let!(:circuit) do
153
+ Circuit.new(buses: [
154
+ Bus.new(number: '30'),
155
+ ])
156
+ end
157
+
158
+ let(:actual_object_matching_condition) do
159
+ circuit.buses.where(number: {operator => 10}).first
160
+ end
161
+
162
+ let(:expected_object_matching_condition) do
163
+ circuit.buses.last
164
+ end
165
+
166
+ let(:actual_object_not_matching_condition) do
167
+ circuit.buses.where(number: {operator => 30}).first
168
+ end
169
+
170
+ it_behaves_like 'a field operator', '$ne'
171
+ end
172
+
173
+ describe '$exists' do
174
+
175
+ context 'true value' do
176
+ let!(:circuit) do
177
+ Circuit.new(buses: [
178
+ Bus.new(number: '30'),
179
+ ])
180
+ end
181
+
182
+ let(:actual_object_matching_condition) do
183
+ circuit.buses.where(number: {operator => true}).first
184
+ end
185
+
186
+ let(:expected_object_matching_condition) do
187
+ circuit.buses.first
188
+ end
189
+
190
+ let(:actual_object_not_matching_condition) do
191
+ circuit.buses.where(number: {operator => false}).first
192
+ end
193
+
194
+ it_behaves_like 'a field operator', '$exists'
195
+ end
196
+
197
+ context 'false value' do
198
+ let!(:circuit) do
199
+ Circuit.new(buses: [
200
+ Bus.new,
201
+ ])
202
+ end
203
+
204
+ let(:actual_object_matching_condition) do
205
+ circuit.buses.where(number: {operator => false}).first
206
+ end
207
+
208
+ let(:expected_object_matching_condition) do
209
+ circuit.buses.first
210
+ end
211
+
212
+ let(:actual_object_not_matching_condition) do
213
+ circuit.buses.where(number: {operator => true}).first
214
+ end
215
+
216
+ it_behaves_like 'a field operator', '$exists'
217
+ end
218
+ end
219
+
220
+ describe '$gt' do
221
+
222
+ let!(:circuit) do
223
+ Circuit.new(buses: [
224
+ Bus.new(number: '10'),
225
+ Bus.new(number: '30'),
226
+ ])
227
+ end
228
+
229
+ let(:actual_object_matching_condition) do
230
+ circuit.buses.where(number: {operator => 15}).first
231
+ end
232
+
233
+ let(:expected_object_matching_condition) do
234
+ circuit.buses.last
235
+ end
236
+
237
+ let(:actual_object_not_matching_condition) do
238
+ # Intentionally equal to the largest bus number
239
+ circuit.buses.where(number: {operator => 30}).first
240
+ end
241
+
242
+ it_behaves_like 'a field operator', '$gt'
243
+ end
244
+
245
+ describe '$gte' do
246
+
247
+ let!(:circuit) do
248
+ Circuit.new(buses: [
249
+ Bus.new(number: '10'),
250
+ Bus.new(number: '30'),
251
+ ])
252
+ end
253
+
254
+ let(:actual_object_matching_condition) do
255
+ # Intentionally equal to the largest bus number
256
+ circuit.buses.where(number: {operator => 30}).first
257
+ end
258
+
259
+ let(:expected_object_matching_condition) do
260
+ circuit.buses.last
261
+ end
262
+
263
+ let(:actual_object_not_matching_condition) do
264
+ circuit.buses.where(number: {operator => 31}).first
265
+ end
266
+
267
+ it_behaves_like 'a field operator', '$gte'
268
+ end
269
+
270
+ describe '$lt' do
271
+
272
+ let!(:circuit) do
273
+ Circuit.new(buses: [
274
+ Bus.new(number: '10'),
275
+ Bus.new(number: '30'),
276
+ ])
277
+ end
278
+
279
+ let(:actual_object_matching_condition) do
280
+ circuit.buses.where(number: {operator => 15}).first
281
+ end
282
+
283
+ let(:expected_object_matching_condition) do
284
+ circuit.buses.first
285
+ end
286
+
287
+ let(:actual_object_not_matching_condition) do
288
+ # Intentionally equal to the smallest bus number
289
+ circuit.buses.where(number: {operator => 10}).first
290
+ end
291
+
292
+ it_behaves_like 'a field operator', '$lt'
293
+ end
294
+
295
+ describe '$lte' do
296
+
297
+ let!(:circuit) do
298
+ Circuit.new(buses: [
299
+ Bus.new(number: '10'),
300
+ Bus.new(number: '30'),
301
+ ])
302
+ end
303
+
304
+ let(:actual_object_matching_condition) do
305
+ # Intentionally equal to the smallest bus number
306
+ circuit.buses.where(number: {operator => 10}).first
307
+ end
308
+
309
+ let(:expected_object_matching_condition) do
310
+ circuit.buses.first
311
+ end
312
+
313
+ let(:actual_object_not_matching_condition) do
314
+ circuit.buses.where(number: {operator => 9}).first
315
+ end
316
+
317
+ it_behaves_like 'a field operator', '$lte'
318
+ end
319
+
320
+ describe '$in' do
321
+
322
+ let!(:circuit) do
323
+ Circuit.new(buses: [
324
+ Bus.new(number: '10'),
325
+ Bus.new(number: '30'),
326
+ ])
327
+ end
328
+
329
+ let(:actual_object_matching_condition) do
330
+ circuit.buses.where(number: {operator => [10, 20]}).first
331
+ end
332
+
333
+ let(:expected_object_matching_condition) do
334
+ circuit.buses.first
335
+ end
336
+
337
+ let(:actual_object_not_matching_condition) do
338
+ circuit.buses.where(number: {operator => [5]}).first
339
+ end
340
+
341
+ it_behaves_like 'a field operator', '$in'
342
+ end
343
+
344
+ describe '$nin' do
345
+
346
+ let!(:circuit) do
347
+ Circuit.new(buses: [
348
+ Bus.new(number: '10'),
349
+ Bus.new(number: '30'),
350
+ ])
351
+ end
352
+
353
+ let(:actual_object_matching_condition) do
354
+ circuit.buses.where(number: {operator => [5, 10]}).first
355
+ end
356
+
357
+ let(:expected_object_matching_condition) do
358
+ circuit.buses.last
359
+ end
360
+
361
+ let(:actual_object_not_matching_condition) do
362
+ circuit.buses.where(number: {operator => [10, 30]}).first
363
+ end
364
+
365
+ it_behaves_like 'a field operator', '$nin'
366
+ end
367
+
368
+ describe '$size' do
369
+
370
+ let!(:person) do
371
+ Person.new(addresses: [
372
+ Address.new(locations: [Location.new]),
373
+ Address.new(locations: [Location.new, Location.new]),
374
+ ])
375
+ end
376
+
377
+ let(:actual_object_matching_condition) do
378
+ person.addresses.where('locations' => {operator => 2}).first
379
+ end
380
+
381
+ let(:expected_object_matching_condition) do
382
+ person.addresses.last
383
+ end
384
+
385
+ let(:actual_object_not_matching_condition) do
386
+ person.addresses.where('locations' => {operator => 3}).first
387
+ end
388
+
389
+ it_behaves_like 'a field operator', '$size'
390
+ end
391
+
392
+ describe '$and' do
393
+ let!(:person) do
394
+ Person.new(addresses: [
395
+ Address.new(locations: [Location.new(name: 'City')]),
396
+ Address.new(locations: [
397
+ # Both criteria are on the same object
398
+ Location.new(name: 'Hall', number: 1),
399
+ Location.new(number: 3),
400
+ ]),
401
+ ])
402
+ end
403
+
404
+ let(:actual_object_matching_condition) do
405
+ person.addresses.where(operator => [
406
+ {'locations.name' => 'Hall'},
407
+ {'locations.number' => 1},
408
+ ]).first
409
+ end
410
+
411
+ let(:expected_object_matching_condition) do
412
+ person.addresses.last
413
+ end
414
+
415
+ let(:actual_object_not_matching_condition) do
416
+ person.addresses.where(operator => [
417
+ {'locations.name' => 'Hall'},
418
+ {'locations.number' => 2},
419
+ ]).first
420
+ end
421
+
422
+ it_behaves_like 'a field operator', '$and'
423
+
424
+ context 'when branches match different embedded objects' do
425
+ let!(:person) do
426
+ Person.new(addresses: [
427
+ Address.new(locations: [Location.new(name: 'City')]),
428
+ Address.new(locations: [
429
+ Location.new(name: 'Hall'),
430
+ Location.new(number: 1),
431
+ ]),
432
+ ])
433
+ end
434
+
435
+ let(:operator) { :$and }
436
+
437
+ it 'finds' do
438
+ expect(actual_object_matching_condition).to eq(expected_object_matching_condition)
439
+ end
440
+
441
+ context 'when $and is on field level' do
442
+ let(:actual_object_matching_condition) do
443
+ person.addresses.where('locations' => {operator => [
444
+ {'name' => 'Hall'},
445
+ {'number' => 1},
446
+ ]}).first
447
+ end
448
+ end
449
+
450
+ it 'is prohibited' do
451
+ # MongoDB prohibits $and operator in values when matching on
452
+ # fields. Mongoid does not have such a prohibition and
453
+ # also returns the address where different locations match the
454
+ # different branches.
455
+ pending 'Mongoid behavior differs from MongoDB'
456
+ expect do
457
+ actual_object_matching_condition
458
+ end.to raise_error(Mongoid::Errors::InvalidFind)
459
+ end
460
+ end
461
+ end
462
+
463
+ describe '$or' do
464
+ let!(:person) do
465
+ Person.new(addresses: [
466
+ Address.new(locations: [Location.new(name: 'City')]),
467
+ Address.new(locations: [
468
+ # Both criteria are on the same object
469
+ Location.new(name: 'Hall', number: 1),
470
+ Location.new(number: 3),
471
+ ]),
472
+ ])
473
+ end
474
+
475
+ let(:actual_object_matching_condition) do
476
+ person.addresses.where(operator => [
477
+ {'locations.name' => 'Hall'},
478
+ {'locations.number' => 4},
479
+ ]).first
480
+ end
481
+
482
+ let(:expected_object_matching_condition) do
483
+ person.addresses.last
484
+ end
485
+
486
+ let(:actual_object_not_matching_condition) do
487
+ person.addresses.where(operator => [
488
+ {'locations.name' => 'Town'},
489
+ {'locations.number' => 4},
490
+ ]).first
491
+ end
492
+
493
+ it_behaves_like 'a field operator', '$or'
494
+
495
+ context 'when branches match different embedded objects' do
496
+ let(:operator) { :$or }
497
+
498
+ context 'when $or is on field level' do
499
+ let(:actual_object_matching_condition) do
500
+ person.addresses.where('locations' => {operator => [
501
+ {'name' => 'Hall'},
502
+ {'number' => 1},
503
+ ]}).first
504
+ end
505
+ end
506
+
507
+ it 'is prohibited' do
508
+ # MongoDB prohibits $and operator in values when matching on
509
+ # fields. Mongoid does not have such a prohibition and
510
+ # also returns the address where different locations match the
511
+ # different branches.
512
+ pending 'Mongoid behavior differs from MongoDB'
513
+ expect do
514
+ actual_object_matching_condition
515
+ end.to raise_error(Mongoid::Errors::InvalidFind)
516
+ end
517
+ end
518
+ end
519
+
520
+ describe '$not' do
521
+ let!(:person) do
522
+ Person.new(addresses: [
523
+ Address.new(locations: [
524
+ Location.new(name: 'City', number: 1),
525
+ ]),
526
+ Address.new(locations: [
527
+ # Both criteria are on the same object
528
+ Location.new(name: 'Hall', number: 1),
529
+ Location.new(number: 3),
530
+ ]),
531
+ ])
532
+ end
533
+
534
+ let(:actual_object_matching_condition) do
535
+ person.addresses.where('locations.name' => {operator =>
536
+ {'$eq' => 'City'},
537
+ }).first
538
+ end
539
+
540
+ let(:expected_object_matching_condition) do
541
+ person.addresses.last
542
+ end
543
+
544
+ let(:actual_object_not_matching_condition) do
545
+ person.addresses.where(operator => [
546
+ {'locations.number' => {'$exists' => true}},
547
+ ]).first
548
+ end
549
+
550
+ it_behaves_like 'a field operator', '$not'
551
+
552
+ context 'when branches match different embedded objects' do
553
+ let!(:person) do
554
+ Person.new(addresses: [
555
+ Address.new(locations: [Location.new(name: 'City')]),
556
+ Address.new(locations: [
557
+ Location.new(name: 'Hall'),
558
+ Location.new(number: 1),
559
+ ]),
560
+ ])
561
+ end
562
+
563
+ let(:operator) { :$not }
564
+
565
+ it 'finds' do
566
+ expect(actual_object_matching_condition).to eq(expected_object_matching_condition)
567
+ end
568
+
569
+ context 'when $not is on field level' do
570
+ let(:actual_object_matching_condition) do
571
+ person.addresses.where('locations' => {operator => [
572
+ {'name' => 'Hall'},
573
+ {'number' => 1},
574
+ ]}).first
575
+ end
576
+ end
577
+
578
+ it 'is prohibited' do
579
+ # MongoDB prohibits $and operator in values when matching on
580
+ # fields. Mongoid does not have such a prohibition and
581
+ # also returns the address where different locations match the
582
+ # different branches.
583
+ pending 'Mongoid behavior differs from MongoDB'
584
+ expect do
585
+ actual_object_matching_condition
586
+ end.to raise_error(Mongoid::Errors::InvalidFind)
587
+ end
588
+ end
589
+ end
590
+ end
591
+
592
+ context 'when attribute is an array' do
593
+ describe 'exact match of array element' do
594
+
595
+ let!(:band) do
596
+ Band.create!(records: [
597
+ Record.new(producers: ['Ferguson', 'Fallon']),
598
+ ])
599
+ end
600
+
601
+ describe 'MongoDB query' do
602
+ let(:found_band) do
603
+ Band.where('records.producers' => 'Ferguson').first
604
+ end
605
+
606
+ it 'finds' do
607
+ expect(found_band).to eq(band)
608
+ end
609
+ end
610
+
611
+ describe 'Mongoid matcher' do
612
+ let(:found_record) do
613
+ band.records.where(producers: 'Ferguson').first
614
+ end
615
+
616
+ it 'finds' do
617
+ expect(found_record).to be band.records.first
618
+ end
619
+ end
620
+ end
621
+
622
+ describe 'regexp match on array element' do
623
+
624
+ let!(:band) do
625
+ Band.create!(records: [
626
+ Record.new(producers: ['Ferguson', 'Fallon']),
627
+ ])
628
+ end
629
+
630
+ describe 'MongoDB query' do
631
+ let(:found_band) do
632
+ Band.where('records.producers' => /Ferg/).first
633
+ end
634
+
635
+ it 'finds' do
636
+ expect(found_band).to eq(band)
637
+ end
638
+ end
639
+
640
+ describe 'Mongoid matcher' do
641
+ let(:found_record) do
642
+ band.records.where(producers: /Ferg/).first
643
+ end
644
+
645
+ it 'finds' do
646
+ expect(found_record).to be band.records.first
647
+ end
648
+ end
649
+ end
650
+
651
+ describe 'range match on array element' do
652
+
653
+ let!(:band) do
654
+ Band.create!(records: [
655
+ Record.new(producers: [123, 456]),
656
+ ])
657
+ end
658
+
659
+ describe 'MongoDB query' do
660
+ let(:found_band) do
661
+ Band.where('records.producers' => 100..200).first
662
+ end
663
+
664
+ it 'finds' do
665
+ expect(found_band).to eq(band)
666
+ end
667
+ end
668
+
669
+ describe 'Mongoid matcher' do
670
+ let(:found_record) do
671
+ band.records.where(producers: 100..200).first
672
+ end
673
+
674
+ it 'finds' do
675
+ expect(found_record).to be band.records.first
676
+ end
677
+ end
678
+ end
679
+ end
680
+ end