mongoid 7.6.1 → 7.6.2
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
- data/lib/config/locales/en.yml +26 -0
- data/lib/mongoid/association/depending.rb +8 -4
- data/lib/mongoid/association/nested/many.rb +38 -3
- data/lib/mongoid/association/nested/nested_buildable.rb +14 -0
- data/lib/mongoid/association/referenced/has_many/proxy.rb +102 -3
- data/lib/mongoid/config.rb +62 -0
- data/lib/mongoid/contextual/aggregable/memory.rb +8 -2
- data/lib/mongoid/contextual/memory.rb +19 -8
- data/lib/mongoid/criteria/queryable/mergeable.rb +12 -0
- data/lib/mongoid/criteria/queryable/selectable.rb +109 -0
- data/lib/mongoid/errors/in_memory_regexp_timeout.rb +26 -0
- data/lib/mongoid/errors.rb +1 -0
- data/lib/mongoid/field_readable.rb +70 -0
- data/lib/mongoid/matchable.rb +6 -1
- data/lib/mongoid/matcher/eq_impl_with_regexp.rb +3 -5
- data/lib/mongoid/matcher/regex.rb +8 -9
- data/lib/mongoid/matcher/regexp_budget.rb +389 -0
- data/lib/mongoid/matcher.rb +1 -0
- data/lib/mongoid/threaded.rb +3 -0
- data/lib/mongoid/version.rb +1 -1
- data/lib/mongoid/warnings.rb +1 -0
- data/spec/integration/app_spec.rb +8 -0
- data/spec/integration/matcher_operator_data/regex.yml +21 -0
- data/spec/integration/matcher_regexp_timeout_spec.rb +215 -0
- data/spec/integration/query_operator_guard_spec.rb +89 -0
- data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +48 -0
- data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +145 -0
- data/spec/mongoid/attributes/nested_spec.rb +202 -8
- data/spec/mongoid/contextual/aggregable/memory_spec.rb +98 -0
- data/spec/mongoid/contextual/memory_spec.rb +196 -0
- data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +2 -0
- data/spec/mongoid/criteria/queryable/selectable_where_spec.rb +200 -0
- data/spec/mongoid/criteria_spec.rb +2 -0
- data/spec/mongoid/matcher/regexp_budget_spec.rb +570 -0
- data/spec/shared/lib/mrss/cluster_config.rb +5 -40
- data/spec/shared/lib/mrss/constraints.rb +7 -46
- data/spec/shared/lib/mrss/docker_runner.rb +10 -21
- data/spec/shared/lib/mrss/eg_config_utils.rb +0 -31
- data/spec/shared/lib/mrss/release/candidate.rb +18 -12
- data/spec/shared/lib/mrss/server_version_registry.rb +1 -5
- data/spec/shared/share/Dockerfile.erb +6 -8
- data/spec/shared/shlib/server.sh +0 -50
- data/spec/shared/shlib/set_env.sh +4 -10
- metadata +11 -2
|
@@ -4,6 +4,33 @@ require "spec_helper"
|
|
|
4
4
|
|
|
5
5
|
describe Mongoid::Attributes::Nested do
|
|
6
6
|
|
|
7
|
+
# Expects the enclosing context to define `build_with_id`, taking an id
|
|
8
|
+
# value and returning the document built with it in nested attributes.
|
|
9
|
+
shared_examples 'rejects non-scalar ids' do |klass_name|
|
|
10
|
+
not_found = /not found for class #{klass_name}/
|
|
11
|
+
|
|
12
|
+
context 'when the id is an operator hash' do
|
|
13
|
+
it 'raises a document not found error' do
|
|
14
|
+
expect { build_with_id({ '$ne' => nil }) }
|
|
15
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound, not_found)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
context 'when the id is an array' do
|
|
20
|
+
it 'raises a document not found error' do
|
|
21
|
+
expect { build_with_id([BSON::ObjectId.new]) }
|
|
22
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound, not_found)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context 'when the id is a malformed object id hash' do
|
|
27
|
+
it 'raises a document not found error' do
|
|
28
|
+
expect { build_with_id({ '$oid' => 'junk' }) }
|
|
29
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound, not_found)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
7
34
|
describe ".accepts_nested_attributes_for" do
|
|
8
35
|
|
|
9
36
|
context "when the autosave option is not defined" do
|
|
@@ -175,6 +202,30 @@ describe Mongoid::Attributes::Nested do
|
|
|
175
202
|
end
|
|
176
203
|
end
|
|
177
204
|
|
|
205
|
+
context 'when the id in the attributes is not a scalar' do
|
|
206
|
+
let(:person) { Person.create! }
|
|
207
|
+
|
|
208
|
+
def build_with_id(id)
|
|
209
|
+
person.update!(posts_attributes: { '0' => { id: id, title: 'Crafted' } })
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
context 'when the association is empty' do
|
|
213
|
+
include_examples 'rejects non-scalar ids', 'Post'
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
context 'when the association is not empty' do
|
|
217
|
+
before { person.posts.create!(title: 'Existing') }
|
|
218
|
+
|
|
219
|
+
include_examples 'rejects non-scalar ids', 'Post'
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
context 'when allow_reparenting_via_nested_attributes is true' do
|
|
223
|
+
config_override :allow_reparenting_via_nested_attributes, true
|
|
224
|
+
|
|
225
|
+
include_examples 'rejects non-scalar ids', 'Post'
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
178
229
|
context "when the relation is a references and referenced in many" do
|
|
179
230
|
|
|
180
231
|
before do
|
|
@@ -198,16 +249,100 @@ describe Mongoid::Attributes::Nested do
|
|
|
198
249
|
)
|
|
199
250
|
end
|
|
200
251
|
|
|
201
|
-
|
|
202
|
-
|
|
252
|
+
context 'when allow_reparenting_via_nested_attributes is false' do
|
|
253
|
+
config_override :allow_reparenting_via_nested_attributes, false
|
|
254
|
+
|
|
255
|
+
it 'raises a document not found error' do
|
|
256
|
+
expect { person }.to raise_error(Mongoid::Errors::DocumentNotFound,
|
|
257
|
+
/Document\(s\) not found for class Preference/)
|
|
258
|
+
end
|
|
203
259
|
end
|
|
204
260
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
261
|
+
context 'when allow_reparenting_via_nested_attributes is true' do
|
|
262
|
+
config_override :allow_reparenting_via_nested_attributes, true
|
|
263
|
+
|
|
264
|
+
it 'sets the nested attributes' do
|
|
265
|
+
expect(person.preferences.map(&:name)).to eq([preference.name])
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
it 'updates attributes of existing document which is added to relation' do
|
|
269
|
+
preference_name = 'updated preference'
|
|
270
|
+
person = Person.new(
|
|
271
|
+
preferences_attributes: { 0 => { id: preference.id, name: preference_name } }
|
|
272
|
+
)
|
|
273
|
+
expect(person.preferences.map(&:name)).to eq([preference_name])
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
context 'when the id does not correspond to an existing document' do
|
|
277
|
+
let(:person) do
|
|
278
|
+
Person.new(
|
|
279
|
+
preferences_attributes: { 0 => { id: BSON::ObjectId.new, name: 'Ghost' } }
|
|
280
|
+
)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
context 'when raise_not_found_error is true' do
|
|
284
|
+
config_override :raise_not_found_error, true
|
|
285
|
+
|
|
286
|
+
it 'raises a document not found error' do
|
|
287
|
+
expect { person }.to raise_error(Mongoid::Errors::DocumentNotFound,
|
|
288
|
+
/Document\(s\) not found for class Preference/)
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
context 'when raise_not_found_error is false' do
|
|
293
|
+
config_override :raise_not_found_error, false
|
|
294
|
+
|
|
295
|
+
it 'raises a document not found error' do
|
|
296
|
+
expect { person }.to raise_error(Mongoid::Errors::DocumentNotFound,
|
|
297
|
+
/Document\(s\) not found for class Preference/)
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
context 'when _destroy is true for a document not in the relation' do
|
|
304
|
+
before do
|
|
305
|
+
Person.send(:undef_method, :preferences_attributes=)
|
|
306
|
+
Person.accepts_nested_attributes_for :preferences, allow_destroy: true
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
let(:person) do
|
|
310
|
+
Person.new(
|
|
311
|
+
preferences_attributes: { 0 => { id: preference.id, _destroy: '1' } }
|
|
312
|
+
)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
it 'does not raise UnknownAttribute' do
|
|
316
|
+
expect { person }.not_to raise_error
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
it 'does not add the document to the relation' do
|
|
320
|
+
expect(person.preferences).to be_empty
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
context 'when the id in the attributes is not a scalar' do
|
|
326
|
+
let(:target) { Person.create! }
|
|
327
|
+
|
|
328
|
+
def build_with_id(id)
|
|
329
|
+
target.update!(preferences_attributes: { 0 => { id: id, name: 'Crafted' } })
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
context 'when the association is empty' do
|
|
333
|
+
include_examples 'rejects non-scalar ids', 'Preference'
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
context 'when the association is not empty' do
|
|
337
|
+
before { target.preferences.create!(name: 'Existing') }
|
|
338
|
+
|
|
339
|
+
include_examples 'rejects non-scalar ids', 'Preference'
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
context 'when allow_reparenting_via_nested_attributes is true' do
|
|
343
|
+
config_override :allow_reparenting_via_nested_attributes, true
|
|
344
|
+
|
|
345
|
+
include_examples 'rejects non-scalar ids', 'Preference'
|
|
211
346
|
end
|
|
212
347
|
end
|
|
213
348
|
end
|
|
@@ -4928,4 +5063,63 @@ describe Mongoid::Attributes::Nested do
|
|
|
4928
5063
|
end
|
|
4929
5064
|
end
|
|
4930
5065
|
end
|
|
5066
|
+
|
|
5067
|
+
context 'when an id in nested attributes belongs to another parent' do
|
|
5068
|
+
let(:victim) { Person.create! }
|
|
5069
|
+
let(:attacker) { Person.create! }
|
|
5070
|
+
|
|
5071
|
+
context 'when the association is a has-many' do
|
|
5072
|
+
before do
|
|
5073
|
+
Person.send(:undef_method, :posts_attributes=)
|
|
5074
|
+
Person.accepts_nested_attributes_for :posts
|
|
5075
|
+
end
|
|
5076
|
+
|
|
5077
|
+
let!(:victim_post) { victim.posts.create!(title: 'Private') }
|
|
5078
|
+
|
|
5079
|
+
it 'does not update the other parent\'s document' do
|
|
5080
|
+
expect { attacker.update!(posts_attributes: { '0' => { id: victim_post.id, title: 'Overwritten' } }) }
|
|
5081
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound)
|
|
5082
|
+
expect(victim_post.reload.title).to eq('Private')
|
|
5083
|
+
end
|
|
5084
|
+
|
|
5085
|
+
it 'does not add the other parent\'s document to the association' do
|
|
5086
|
+
expect { attacker.update!(posts_attributes: { '0' => { id: victim_post.id, title: 'Overwritten' } }) }
|
|
5087
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound)
|
|
5088
|
+
expect(attacker.reload.posts).to be_empty
|
|
5089
|
+
end
|
|
5090
|
+
|
|
5091
|
+
it 'does not update via a direct association assignment' do
|
|
5092
|
+
expect { attacker.update!(posts: { '0' => { id: victim_post.id, title: 'Overwritten' } }) }
|
|
5093
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound)
|
|
5094
|
+
expect(victim_post.reload.title).to eq('Private')
|
|
5095
|
+
end
|
|
5096
|
+
end
|
|
5097
|
+
|
|
5098
|
+
context 'when the association is a has-and-belongs-to-many' do
|
|
5099
|
+
before do
|
|
5100
|
+
Person.send(:undef_method, :preferences_attributes=)
|
|
5101
|
+
Person.accepts_nested_attributes_for :preferences
|
|
5102
|
+
end
|
|
5103
|
+
|
|
5104
|
+
let!(:victim_preference) { victim.preferences.create!(name: 'Private') }
|
|
5105
|
+
|
|
5106
|
+
it 'does not update the other parent\'s document' do
|
|
5107
|
+
expect { attacker.update!(preferences_attributes: { '0' => { id: victim_preference.id, name: 'Overwritten' } }) }
|
|
5108
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound)
|
|
5109
|
+
expect(victim_preference.reload.name).to eq('Private')
|
|
5110
|
+
end
|
|
5111
|
+
|
|
5112
|
+
it 'does not add the other parent\'s document to the association' do
|
|
5113
|
+
expect { attacker.update!(preferences_attributes: { '0' => { id: victim_preference.id, name: 'Overwritten' } }) }
|
|
5114
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound)
|
|
5115
|
+
expect(attacker.reload.preferences).to be_empty
|
|
5116
|
+
end
|
|
5117
|
+
|
|
5118
|
+
it 'does not update via a direct association assignment' do
|
|
5119
|
+
expect { attacker.update!(preferences: { '0' => { id: victim_preference.id, name: 'Overwritten' } }) }
|
|
5120
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound)
|
|
5121
|
+
expect(victim_preference.reload.name).to eq('Private')
|
|
5122
|
+
end
|
|
5123
|
+
end
|
|
5124
|
+
end
|
|
4931
5125
|
end
|
|
@@ -4,6 +4,10 @@ require "spec_helper"
|
|
|
4
4
|
|
|
5
5
|
describe Mongoid::Contextual::Aggregable::Memory do
|
|
6
6
|
|
|
7
|
+
let(:context) do
|
|
8
|
+
Mongoid::Contextual::Memory.new(criteria)
|
|
9
|
+
end
|
|
10
|
+
|
|
7
11
|
describe "#aggregates" do
|
|
8
12
|
let(:context) do
|
|
9
13
|
Mongoid::Contextual::Memory.new(criteria)
|
|
@@ -332,4 +336,98 @@ describe Mongoid::Contextual::Aggregable::Memory do
|
|
|
332
336
|
end
|
|
333
337
|
end
|
|
334
338
|
end
|
|
339
|
+
|
|
340
|
+
context "when the field name is a method name" do
|
|
341
|
+
|
|
342
|
+
let!(:depeche) do
|
|
343
|
+
Band.create!(name: "Depeche Mode", likes: 1000)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
let(:criteria) do
|
|
347
|
+
Band.all.tap do |crit|
|
|
348
|
+
crit.documents = [ depeche ]
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
it "does not call the method for sum" do
|
|
353
|
+
expect(depeche).not_to receive(:delete)
|
|
354
|
+
expect(context.sum(:delete)).to eq(0)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
it "does not call the method for min" do
|
|
358
|
+
expect(depeche).not_to receive(:delete)
|
|
359
|
+
expect(context.min(:delete)).to be_nil
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
it "does not call the method for max" do
|
|
363
|
+
expect(depeche).not_to receive(:delete)
|
|
364
|
+
expect(context.max(:delete)).to be_nil
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
it "does not call the method for avg" do
|
|
368
|
+
expect(depeche).not_to receive(:delete)
|
|
369
|
+
expect(context.avg(:delete)).to eq(0.0)
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
it "does not call the method for aggregates" do
|
|
373
|
+
expect(depeche).not_to receive(:delete)
|
|
374
|
+
expect(context.aggregates(:delete))
|
|
375
|
+
.to eq("count" => 0, "avg" => 0.0, "max" => nil, "min" => nil, "sum" => 0)
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
it "does not call a private method for avg" do
|
|
379
|
+
expect(depeche).not_to receive(:exit)
|
|
380
|
+
expect(context.avg(:exit)).to eq(0.0)
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
context "when the field is not defined" do
|
|
385
|
+
|
|
386
|
+
let!(:depeche) do
|
|
387
|
+
Band.create!(name: "Depeche Mode", likes: 1000)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
let(:criteria) do
|
|
391
|
+
Band.all.tap do |crit|
|
|
392
|
+
crit.documents = [ depeche ]
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
it "returns zero for sum" do
|
|
397
|
+
expect(context.sum(:not_a_field)).to eq(0)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
it "returns nil for min" do
|
|
401
|
+
expect(context.min(:not_a_field)).to be_nil
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
it "returns nil for max" do
|
|
405
|
+
expect(context.max(:not_a_field)).to be_nil
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
it "returns zero for avg" do
|
|
409
|
+
expect(context.avg(:not_a_field)).to eq(0.0)
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
context "when the field is the foreign key of a many to many association" do
|
|
414
|
+
|
|
415
|
+
let!(:preference) do
|
|
416
|
+
Preference.create!(name: "nature")
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
let!(:person) do
|
|
420
|
+
Person.new(preferences: [ preference ])
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
let(:criteria) do
|
|
424
|
+
Person.all.tap do |crit|
|
|
425
|
+
crit.documents = [ person ]
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
it "aggregates the stored ids" do
|
|
430
|
+
expect(context.max(:preference_ids)).to eq([ preference.id ])
|
|
431
|
+
end
|
|
432
|
+
end
|
|
335
433
|
end
|
|
@@ -438,6 +438,32 @@ describe Mongoid::Contextual::Memory do
|
|
|
438
438
|
}.to raise_exception(Mongoid::Errors::InMemoryCollationNotSupported)
|
|
439
439
|
end
|
|
440
440
|
end
|
|
441
|
+
|
|
442
|
+
context "when the field name is a method name" do
|
|
443
|
+
|
|
444
|
+
let!(:person) do
|
|
445
|
+
Person.create!(ssn: "secret-ssn")
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
let!(:address) do
|
|
449
|
+
person.addresses.create!(street: "hobrecht")
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
let(:criteria) do
|
|
453
|
+
Address.all.tap do |crit|
|
|
454
|
+
crit.documents = [ address ]
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
let(:context) do
|
|
459
|
+
described_class.new(criteria)
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
it "does not call the method" do
|
|
463
|
+
expect(address).not_to receive(:destroy)
|
|
464
|
+
expect(context.distinct(:destroy)).to eq([ nil ])
|
|
465
|
+
end
|
|
466
|
+
end
|
|
441
467
|
end
|
|
442
468
|
|
|
443
469
|
context "when legacy_pluck_distinct is false" do
|
|
@@ -632,6 +658,28 @@ describe Mongoid::Contextual::Memory do
|
|
|
632
658
|
expect(context.distinct("label.sales")).to eq([ BigDecimal("1E2") ])
|
|
633
659
|
end
|
|
634
660
|
end
|
|
661
|
+
|
|
662
|
+
context "when the field name is a method name" do
|
|
663
|
+
|
|
664
|
+
let!(:person) do
|
|
665
|
+
Person.create!(ssn: "secret-ssn")
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
let!(:address) do
|
|
669
|
+
person.addresses.create!(street: "hobrecht")
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
let(:criteria) do
|
|
673
|
+
Address.all.tap do |crit|
|
|
674
|
+
crit.documents = [ address ]
|
|
675
|
+
end
|
|
676
|
+
end
|
|
677
|
+
|
|
678
|
+
it "does not call the method" do
|
|
679
|
+
expect(address).not_to receive(:destroy)
|
|
680
|
+
expect(context.distinct(:destroy)).to eq([ nil ])
|
|
681
|
+
end
|
|
682
|
+
end
|
|
635
683
|
end
|
|
636
684
|
end
|
|
637
685
|
|
|
@@ -1747,6 +1795,121 @@ describe Mongoid::Contextual::Memory do
|
|
|
1747
1795
|
])
|
|
1748
1796
|
end
|
|
1749
1797
|
end
|
|
1798
|
+
|
|
1799
|
+
context "when plucking a dynamic attribute" do
|
|
1800
|
+
|
|
1801
|
+
let(:criteria) do
|
|
1802
|
+
Band.all.tap do |crit|
|
|
1803
|
+
crit.documents = [ Band.create!(name: "Depeche Mode", mood: "dark") ]
|
|
1804
|
+
end
|
|
1805
|
+
end
|
|
1806
|
+
|
|
1807
|
+
it "returns the value from the attributes" do
|
|
1808
|
+
expect(context.pluck(:mood)).to eq([ "dark" ])
|
|
1809
|
+
end
|
|
1810
|
+
end
|
|
1811
|
+
|
|
1812
|
+
context "when the field name is a method name" do
|
|
1813
|
+
|
|
1814
|
+
let!(:person) do
|
|
1815
|
+
Person.create!(ssn: "secret-ssn")
|
|
1816
|
+
end
|
|
1817
|
+
|
|
1818
|
+
let!(:address) do
|
|
1819
|
+
person.addresses.create!(street: "hobrecht")
|
|
1820
|
+
end
|
|
1821
|
+
|
|
1822
|
+
let(:criteria) do
|
|
1823
|
+
Address.all.tap do |crit|
|
|
1824
|
+
crit.documents = [ address ]
|
|
1825
|
+
end
|
|
1826
|
+
end
|
|
1827
|
+
|
|
1828
|
+
it "does not call the method" do
|
|
1829
|
+
expect(address).not_to receive(:destroy)
|
|
1830
|
+
expect(context.pluck(:destroy)).to eq([ nil ])
|
|
1831
|
+
end
|
|
1832
|
+
|
|
1833
|
+
it "does not return the attributes hash" do
|
|
1834
|
+
expect(context.pluck(:attributes)).to eq([ nil ])
|
|
1835
|
+
end
|
|
1836
|
+
|
|
1837
|
+
it "does not traverse to the parent document" do
|
|
1838
|
+
expect(context.pluck("_root.ssn")).to eq([ nil ])
|
|
1839
|
+
end
|
|
1840
|
+
|
|
1841
|
+
it "does not destroy the parent document" do
|
|
1842
|
+
expect(context.pluck("_root.destroy")).to eq([ nil ])
|
|
1843
|
+
expect(Person.where(_id: person.id).count).to eq(1)
|
|
1844
|
+
end
|
|
1845
|
+
end
|
|
1846
|
+
|
|
1847
|
+
context "when plucking a belongs_to association" do
|
|
1848
|
+
|
|
1849
|
+
let!(:band) do
|
|
1850
|
+
Band.create!(name: "Depeche Mode")
|
|
1851
|
+
end
|
|
1852
|
+
|
|
1853
|
+
let!(:artist) do
|
|
1854
|
+
Artist.create!(band: band)
|
|
1855
|
+
end
|
|
1856
|
+
|
|
1857
|
+
let(:criteria) do
|
|
1858
|
+
Artist.all.tap do |crit|
|
|
1859
|
+
crit.documents = [ artist ]
|
|
1860
|
+
end
|
|
1861
|
+
end
|
|
1862
|
+
|
|
1863
|
+
it "traverses to the associated document" do
|
|
1864
|
+
expect(context.pluck("band.name")).to eq([ "Depeche Mode" ])
|
|
1865
|
+
end
|
|
1866
|
+
|
|
1867
|
+
it "returns the document for the association name" do
|
|
1868
|
+
expect(context.pluck(:band)).to eq([ band ])
|
|
1869
|
+
end
|
|
1870
|
+
|
|
1871
|
+
it "returns the id for the foreign key" do
|
|
1872
|
+
expect(context.pluck(:band_id)).to eq([ band.id ])
|
|
1873
|
+
end
|
|
1874
|
+
end
|
|
1875
|
+
|
|
1876
|
+
context "when plucking the foreign key of a many to many association" do
|
|
1877
|
+
|
|
1878
|
+
let!(:preference) do
|
|
1879
|
+
Preference.create!(name: "nature")
|
|
1880
|
+
end
|
|
1881
|
+
|
|
1882
|
+
let!(:person) do
|
|
1883
|
+
Person.create!(preferences: [ preference ])
|
|
1884
|
+
end
|
|
1885
|
+
|
|
1886
|
+
let(:criteria) do
|
|
1887
|
+
Person.all.tap do |crit|
|
|
1888
|
+
crit.documents = [ person ]
|
|
1889
|
+
end
|
|
1890
|
+
end
|
|
1891
|
+
|
|
1892
|
+
it "returns the stored ids" do
|
|
1893
|
+
expect(context.pluck(:preference_ids)).to eq([ [ preference.id ] ])
|
|
1894
|
+
end
|
|
1895
|
+
end
|
|
1896
|
+
|
|
1897
|
+
context "when the field path has an empty segment" do
|
|
1898
|
+
|
|
1899
|
+
let!(:band) do
|
|
1900
|
+
Band.create!(name: "Depeche Mode", label: Label.new(name: "Mute"))
|
|
1901
|
+
end
|
|
1902
|
+
|
|
1903
|
+
let(:criteria) do
|
|
1904
|
+
Band.all.tap do |crit|
|
|
1905
|
+
crit.documents = [ band ]
|
|
1906
|
+
end
|
|
1907
|
+
end
|
|
1908
|
+
|
|
1909
|
+
it "returns nil" do
|
|
1910
|
+
expect(context.pluck("label..name")).to eq([ nil ])
|
|
1911
|
+
end
|
|
1912
|
+
end
|
|
1750
1913
|
end
|
|
1751
1914
|
end
|
|
1752
1915
|
|
|
@@ -1769,6 +1932,39 @@ describe Mongoid::Contextual::Memory do
|
|
|
1769
1932
|
end
|
|
1770
1933
|
end
|
|
1771
1934
|
|
|
1935
|
+
describe "#tally" do
|
|
1936
|
+
|
|
1937
|
+
let(:context) do
|
|
1938
|
+
described_class.new(criteria)
|
|
1939
|
+
end
|
|
1940
|
+
|
|
1941
|
+
context "when the field name is a method name" do
|
|
1942
|
+
|
|
1943
|
+
let!(:person) do
|
|
1944
|
+
Person.create!(ssn: "secret-ssn")
|
|
1945
|
+
end
|
|
1946
|
+
|
|
1947
|
+
let!(:address) do
|
|
1948
|
+
person.addresses.create!(street: "hobrecht")
|
|
1949
|
+
end
|
|
1950
|
+
|
|
1951
|
+
let(:criteria) do
|
|
1952
|
+
Address.all.tap do |crit|
|
|
1953
|
+
crit.documents = [ address ]
|
|
1954
|
+
end
|
|
1955
|
+
end
|
|
1956
|
+
|
|
1957
|
+
it "does not call the method" do
|
|
1958
|
+
expect(address).not_to receive(:destroy)
|
|
1959
|
+
expect(context.tally(:destroy)).to eq(nil => 1)
|
|
1960
|
+
end
|
|
1961
|
+
|
|
1962
|
+
it "does not disclose the parent document fields" do
|
|
1963
|
+
expect(context.tally("_root.ssn")).to eq(nil => 1)
|
|
1964
|
+
end
|
|
1965
|
+
end
|
|
1966
|
+
end
|
|
1967
|
+
|
|
1772
1968
|
describe "#skip" do
|
|
1773
1969
|
|
|
1774
1970
|
let(:hobrecht) do
|
|
@@ -1801,6 +1801,8 @@ describe Mongoid::Criteria::Queryable::Selectable do
|
|
|
1801
1801
|
end
|
|
1802
1802
|
|
|
1803
1803
|
context 'when the following criteria uses string were form' do
|
|
1804
|
+
# String criteria compile to $where, which requires the opt-in.
|
|
1805
|
+
config_override :allow_unsafe_query_operators, true
|
|
1804
1806
|
|
|
1805
1807
|
let(:selection) do
|
|
1806
1808
|
query.not.where('hello world')
|