mongoid 7.0.5 → 7.0.6

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