mongoid 7.0.5 → 7.0.6

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.
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+
2
3
  module Mongoid
3
- VERSION = "7.0.5"
4
+ VERSION = "7.0.6"
4
5
  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,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