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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/LICENSE +1 -0
- data/README.md +2 -1
- data/lib/mongoid.rb +1 -0
- data/lib/mongoid/attributes.rb +28 -20
- data/lib/mongoid/attributes/dynamic.rb +15 -14
- data/lib/mongoid/config/environment.rb +21 -8
- data/lib/mongoid/criteria/queryable/mergeable.rb +5 -4
- data/lib/mongoid/criteria/queryable/selectable.rb +2 -3
- data/lib/mongoid/matchable.rb +14 -15
- data/lib/mongoid/matchable/all.rb +4 -3
- data/lib/mongoid/matchable/default.rb +71 -24
- data/lib/mongoid/version.rb +2 -1
- data/spec/integration/criteria/time_with_zone_spec.rb +32 -0
- data/spec/integration/matchable_spec.rb +680 -0
- data/spec/lite_spec_helper.rb +4 -1
- data/spec/mongoid/attributes/dynamic_spec.rb +153 -0
- data/spec/mongoid/attributes_spec.rb +19 -7
- data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +762 -0
- data/spec/mongoid/criteria/queryable/selectable_spec.rb +5 -224
- data/spec/mongoid/document_fields_spec.rb +88 -0
- data/spec/mongoid/matchable/default_spec.rb +9 -2
- data/spec/mongoid/validatable/uniqueness_spec.rb +33 -6
- data/spec/support/expectations.rb +17 -3
- metadata +457 -442
- metadata.gz.sig +0 -0
data/lib/mongoid/version.rb
CHANGED
@@ -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
|