mongoid 8.1.12 → 8.1.13
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 +100 -1
- data/lib/mongoid/config.rb +62 -0
- data/lib/mongoid/contextual/aggregable/memory.rb +5 -2
- data/lib/mongoid/contextual/memory.rb +18 -7
- 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/associations/has_and_belongs_to_many_spec.rb +17 -2
- data/spec/integration/dots_and_dollars_spec.rb +12 -2
- 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 +204 -10
- data/spec/mongoid/contextual/aggregable/memory_spec.rb +91 -0
- data/spec/mongoid/contextual/memory_spec.rb +177 -0
- data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +2 -0
- data/spec/mongoid/criteria/queryable/selectable_where_spec.rb +235 -0
- data/spec/mongoid/criteria_spec.rb +2 -0
- data/spec/mongoid/matcher/regexp_budget_spec.rb +570 -0
- metadata +11 -2
|
@@ -7,6 +7,33 @@ require_relative './nested_spec_models'
|
|
|
7
7
|
|
|
8
8
|
describe Mongoid::Attributes::Nested do
|
|
9
9
|
|
|
10
|
+
# Expects the enclosing context to define `build_with_id`, taking an id
|
|
11
|
+
# value and returning the document built with it in nested attributes.
|
|
12
|
+
shared_examples 'rejects non-scalar ids' do |klass_name|
|
|
13
|
+
not_found = /not found for class #{klass_name}/
|
|
14
|
+
|
|
15
|
+
context 'when the id is an operator hash' do
|
|
16
|
+
it 'raises a document not found error' do
|
|
17
|
+
expect { build_with_id({ '$ne' => nil }) }
|
|
18
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound, not_found)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
context 'when the id is an array' do
|
|
23
|
+
it 'raises a document not found error' do
|
|
24
|
+
expect { build_with_id([BSON::ObjectId.new]) }
|
|
25
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound, not_found)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context 'when the id is a malformed object id hash' do
|
|
30
|
+
it 'raises a document not found error' do
|
|
31
|
+
expect { build_with_id({ '$oid' => 'junk' }) }
|
|
32
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound, not_found)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
10
37
|
describe ".accepts_nested_attributes_for" do
|
|
11
38
|
|
|
12
39
|
context "when the autosave option is not defined" do
|
|
@@ -178,6 +205,30 @@ describe Mongoid::Attributes::Nested do
|
|
|
178
205
|
end
|
|
179
206
|
end
|
|
180
207
|
|
|
208
|
+
context 'when the id in the attributes is not a scalar' do
|
|
209
|
+
let(:person) { Person.create! }
|
|
210
|
+
|
|
211
|
+
def build_with_id(id)
|
|
212
|
+
person.update!(posts_attributes: { '0' => { id: id, title: 'Crafted' } })
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
context 'when the association is empty' do
|
|
216
|
+
include_examples 'rejects non-scalar ids', 'Post'
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
context 'when the association is not empty' do
|
|
220
|
+
before { person.posts.create!(title: 'Existing') }
|
|
221
|
+
|
|
222
|
+
include_examples 'rejects non-scalar ids', 'Post'
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
context 'when allow_reparenting_via_nested_attributes is true' do
|
|
226
|
+
config_override :allow_reparenting_via_nested_attributes, true
|
|
227
|
+
|
|
228
|
+
include_examples 'rejects non-scalar ids', 'Post'
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
181
232
|
context "when the relation is a references and referenced in many" do
|
|
182
233
|
|
|
183
234
|
before do
|
|
@@ -201,16 +252,100 @@ describe Mongoid::Attributes::Nested do
|
|
|
201
252
|
)
|
|
202
253
|
end
|
|
203
254
|
|
|
204
|
-
|
|
205
|
-
|
|
255
|
+
context 'when allow_reparenting_via_nested_attributes is false' do
|
|
256
|
+
config_override :allow_reparenting_via_nested_attributes, false
|
|
257
|
+
|
|
258
|
+
it 'raises a document not found error' do
|
|
259
|
+
expect { person }.to raise_error(Mongoid::Errors::DocumentNotFound,
|
|
260
|
+
/Document\(s\) not found for class Preference/)
|
|
261
|
+
end
|
|
206
262
|
end
|
|
207
263
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
264
|
+
context 'when allow_reparenting_via_nested_attributes is true' do
|
|
265
|
+
config_override :allow_reparenting_via_nested_attributes, true
|
|
266
|
+
|
|
267
|
+
it 'sets the nested attributes' do
|
|
268
|
+
expect(person.preferences.map(&:name)).to eq([preference.name])
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
it 'updates attributes of existing document which is added to relation' do
|
|
272
|
+
preference_name = 'updated preference'
|
|
273
|
+
person = Person.new(
|
|
274
|
+
preferences_attributes: { 0 => { id: preference.id, name: preference_name } }
|
|
275
|
+
)
|
|
276
|
+
expect(person.preferences.map(&:name)).to eq([preference_name])
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
context 'when the id does not correspond to an existing document' do
|
|
280
|
+
let(:person) do
|
|
281
|
+
Person.new(
|
|
282
|
+
preferences_attributes: { 0 => { id: BSON::ObjectId.new, name: 'Ghost' } }
|
|
283
|
+
)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
context 'when raise_not_found_error is true' do
|
|
287
|
+
config_override :raise_not_found_error, true
|
|
288
|
+
|
|
289
|
+
it 'raises a document not found error' do
|
|
290
|
+
expect { person }.to raise_error(Mongoid::Errors::DocumentNotFound,
|
|
291
|
+
/Document\(s\) not found for class Preference/)
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
context 'when raise_not_found_error is false' do
|
|
296
|
+
config_override :raise_not_found_error, false
|
|
297
|
+
|
|
298
|
+
it 'raises a document not found error' do
|
|
299
|
+
expect { person }.to raise_error(Mongoid::Errors::DocumentNotFound,
|
|
300
|
+
/Document\(s\) not found for class Preference/)
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
context 'when _destroy is true for a document not in the relation' do
|
|
307
|
+
before do
|
|
308
|
+
Person.send(:undef_method, :preferences_attributes=)
|
|
309
|
+
Person.accepts_nested_attributes_for :preferences, allow_destroy: true
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
let(:person) do
|
|
313
|
+
Person.new(
|
|
314
|
+
preferences_attributes: { 0 => { id: preference.id, _destroy: '1' } }
|
|
315
|
+
)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
it 'does not raise UnknownAttribute' do
|
|
319
|
+
expect { person }.not_to raise_error
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
it 'does not add the document to the relation' do
|
|
323
|
+
expect(person.preferences).to be_empty
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
context 'when the id in the attributes is not a scalar' do
|
|
329
|
+
let(:target) { Person.create! }
|
|
330
|
+
|
|
331
|
+
def build_with_id(id)
|
|
332
|
+
target.update!(preferences_attributes: { 0 => { id: id, name: 'Crafted' } })
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
context 'when the association is empty' do
|
|
336
|
+
include_examples 'rejects non-scalar ids', 'Preference'
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
context 'when the association is not empty' do
|
|
340
|
+
before { target.preferences.create!(name: 'Existing') }
|
|
341
|
+
|
|
342
|
+
include_examples 'rejects non-scalar ids', 'Preference'
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
context 'when allow_reparenting_via_nested_attributes is true' do
|
|
346
|
+
config_override :allow_reparenting_via_nested_attributes, true
|
|
347
|
+
|
|
348
|
+
include_examples 'rejects non-scalar ids', 'Preference'
|
|
214
349
|
end
|
|
215
350
|
end
|
|
216
351
|
end
|
|
@@ -3015,7 +3150,7 @@ describe Mongoid::Attributes::Nested do
|
|
|
3015
3150
|
{ "0" =>
|
|
3016
3151
|
{ "id" => BSON::ObjectId.new.to_s, "title" => "Rogue" }
|
|
3017
3152
|
}
|
|
3018
|
-
}.to raise_error(Mongoid::Errors::DocumentNotFound, /Document
|
|
3153
|
+
}.to raise_error(Mongoid::Errors::DocumentNotFound, /Document not found for class Post/)
|
|
3019
3154
|
end
|
|
3020
3155
|
end
|
|
3021
3156
|
end
|
|
@@ -3049,7 +3184,7 @@ describe Mongoid::Attributes::Nested do
|
|
|
3049
3184
|
expect {
|
|
3050
3185
|
person.posts_attributes =
|
|
3051
3186
|
{ "foo" => { "id" => "test", "title" => "Test" } }
|
|
3052
|
-
}.to raise_error(Mongoid::Errors::DocumentNotFound, /Document
|
|
3187
|
+
}.to raise_error(Mongoid::Errors::DocumentNotFound, /Document not found for class Post/)
|
|
3053
3188
|
end
|
|
3054
3189
|
end
|
|
3055
3190
|
end
|
|
@@ -4997,4 +5132,63 @@ describe Mongoid::Attributes::Nested do
|
|
|
4997
5132
|
end
|
|
4998
5133
|
end
|
|
4999
5134
|
end
|
|
5135
|
+
|
|
5136
|
+
context 'when an id in nested attributes belongs to another parent' do
|
|
5137
|
+
let(:victim) { Person.create! }
|
|
5138
|
+
let(:attacker) { Person.create! }
|
|
5139
|
+
|
|
5140
|
+
context 'when the association is a has-many' do
|
|
5141
|
+
before do
|
|
5142
|
+
Person.send(:undef_method, :posts_attributes=)
|
|
5143
|
+
Person.accepts_nested_attributes_for :posts
|
|
5144
|
+
end
|
|
5145
|
+
|
|
5146
|
+
let!(:victim_post) { victim.posts.create!(title: 'Private') }
|
|
5147
|
+
|
|
5148
|
+
it 'does not update the other parent\'s document' do
|
|
5149
|
+
expect { attacker.update!(posts_attributes: { '0' => { id: victim_post.id, title: 'Overwritten' } }) }
|
|
5150
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound)
|
|
5151
|
+
expect(victim_post.reload.title).to eq('Private')
|
|
5152
|
+
end
|
|
5153
|
+
|
|
5154
|
+
it 'does not add the other parent\'s document to the association' do
|
|
5155
|
+
expect { attacker.update!(posts_attributes: { '0' => { id: victim_post.id, title: 'Overwritten' } }) }
|
|
5156
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound)
|
|
5157
|
+
expect(attacker.reload.posts).to be_empty
|
|
5158
|
+
end
|
|
5159
|
+
|
|
5160
|
+
it 'does not update via a direct association assignment' do
|
|
5161
|
+
expect { attacker.update!(posts: { '0' => { id: victim_post.id, title: 'Overwritten' } }) }
|
|
5162
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound)
|
|
5163
|
+
expect(victim_post.reload.title).to eq('Private')
|
|
5164
|
+
end
|
|
5165
|
+
end
|
|
5166
|
+
|
|
5167
|
+
context 'when the association is a has-and-belongs-to-many' do
|
|
5168
|
+
before do
|
|
5169
|
+
Person.send(:undef_method, :preferences_attributes=)
|
|
5170
|
+
Person.accepts_nested_attributes_for :preferences
|
|
5171
|
+
end
|
|
5172
|
+
|
|
5173
|
+
let!(:victim_preference) { victim.preferences.create!(name: 'Private') }
|
|
5174
|
+
|
|
5175
|
+
it 'does not update the other parent\'s document' do
|
|
5176
|
+
expect { attacker.update!(preferences_attributes: { '0' => { id: victim_preference.id, name: 'Overwritten' } }) }
|
|
5177
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound)
|
|
5178
|
+
expect(victim_preference.reload.name).to eq('Private')
|
|
5179
|
+
end
|
|
5180
|
+
|
|
5181
|
+
it 'does not add the other parent\'s document to the association' do
|
|
5182
|
+
expect { attacker.update!(preferences_attributes: { '0' => { id: victim_preference.id, name: 'Overwritten' } }) }
|
|
5183
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound)
|
|
5184
|
+
expect(attacker.reload.preferences).to be_empty
|
|
5185
|
+
end
|
|
5186
|
+
|
|
5187
|
+
it 'does not update via a direct association assignment' do
|
|
5188
|
+
expect { attacker.update!(preferences: { '0' => { id: victim_preference.id, name: 'Overwritten' } }) }
|
|
5189
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound)
|
|
5190
|
+
expect(victim_preference.reload.name).to eq('Private')
|
|
5191
|
+
end
|
|
5192
|
+
end
|
|
5193
|
+
end
|
|
5000
5194
|
end
|
|
@@ -570,4 +570,95 @@ describe Mongoid::Contextual::Aggregable::Memory do
|
|
|
570
570
|
end
|
|
571
571
|
end
|
|
572
572
|
end
|
|
573
|
+
|
|
574
|
+
context 'when the field name is a method name' do
|
|
575
|
+
let!(:depeche) do
|
|
576
|
+
Band.create!(name: 'Depeche Mode', likes: 1000)
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
let(:criteria) do
|
|
580
|
+
Band.all.tap do |crit|
|
|
581
|
+
crit.documents = [ depeche ]
|
|
582
|
+
end
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
it 'does not call the method for sum' do
|
|
586
|
+
expect(depeche).not_to receive(:delete)
|
|
587
|
+
expect(context.sum(:delete)).to eq(0)
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
it 'does not call the method for min' do
|
|
591
|
+
expect(depeche).not_to receive(:delete)
|
|
592
|
+
expect(context.min(:delete)).to be_nil
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
it 'does not call the method for max' do
|
|
596
|
+
expect(depeche).not_to receive(:delete)
|
|
597
|
+
expect(context.max(:delete)).to be_nil
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
it 'does not call the method for avg' do
|
|
601
|
+
expect(depeche).not_to receive(:delete)
|
|
602
|
+
expect(context.avg(:delete)).to be_nil
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
it 'does not call the method for aggregates' do
|
|
606
|
+
expect(depeche).not_to receive(:delete)
|
|
607
|
+
expect(context.aggregates(:delete))
|
|
608
|
+
.to eq('count' => 0, 'avg' => nil, 'max' => nil, 'min' => nil, 'sum' => 0)
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
it 'does not call a private method for avg' do
|
|
612
|
+
expect(depeche).not_to receive(:exit)
|
|
613
|
+
expect(context.avg(:exit)).to be_nil
|
|
614
|
+
end
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
context 'when the field is not defined' do
|
|
618
|
+
let!(:depeche) do
|
|
619
|
+
Band.create!(name: 'Depeche Mode', likes: 1000)
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
let(:criteria) do
|
|
623
|
+
Band.all.tap do |crit|
|
|
624
|
+
crit.documents = [ depeche ]
|
|
625
|
+
end
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
it 'returns zero for sum' do
|
|
629
|
+
expect(context.sum(:not_a_field)).to eq(0)
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
it 'returns nil for min' do
|
|
633
|
+
expect(context.min(:not_a_field)).to be_nil
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
it 'returns nil for max' do
|
|
637
|
+
expect(context.max(:not_a_field)).to be_nil
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
it 'returns nil for avg' do
|
|
641
|
+
expect(context.avg(:not_a_field)).to be_nil
|
|
642
|
+
end
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
context 'when the field is the foreign key of a many to many association' do
|
|
646
|
+
let!(:preference) do
|
|
647
|
+
Preference.create!(name: 'nature')
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
let!(:person) do
|
|
651
|
+
Person.new(preferences: [ preference ])
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
let(:criteria) do
|
|
655
|
+
Person.all.tap do |crit|
|
|
656
|
+
crit.documents = [ person ]
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
it 'aggregates the stored ids' do
|
|
661
|
+
expect(context.max(:preference_ids)).to eq([ preference.id ])
|
|
662
|
+
end
|
|
663
|
+
end
|
|
573
664
|
end
|
|
@@ -653,6 +653,27 @@ describe Mongoid::Contextual::Memory do
|
|
|
653
653
|
end
|
|
654
654
|
end
|
|
655
655
|
end
|
|
656
|
+
|
|
657
|
+
context 'when the field name is a method name' do
|
|
658
|
+
let!(:person) do
|
|
659
|
+
Person.create!(ssn: 'secret-ssn')
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
let!(:address) do
|
|
663
|
+
person.addresses.create!(street: 'hobrecht')
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
let(:criteria) do
|
|
667
|
+
Address.all.tap do |crit|
|
|
668
|
+
crit.documents = [ address ]
|
|
669
|
+
end
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
it 'does not call the method' do
|
|
673
|
+
expect(address).not_to receive(:destroy)
|
|
674
|
+
expect(context.distinct(:destroy)).to eq([ nil ])
|
|
675
|
+
end
|
|
676
|
+
end
|
|
656
677
|
end
|
|
657
678
|
|
|
658
679
|
describe "#each" do
|
|
@@ -1996,6 +2017,116 @@ describe Mongoid::Contextual::Memory do
|
|
|
1996
2017
|
end
|
|
1997
2018
|
end
|
|
1998
2019
|
end
|
|
2020
|
+
|
|
2021
|
+
context 'when plucking a dynamic attribute' do
|
|
2022
|
+
let(:criteria) do
|
|
2023
|
+
Band.all.tap do |crit|
|
|
2024
|
+
crit.documents = [ Band.create!(name: 'Depeche Mode', mood: 'dark') ]
|
|
2025
|
+
end
|
|
2026
|
+
end
|
|
2027
|
+
|
|
2028
|
+
it 'returns the value from the attributes' do
|
|
2029
|
+
expect(context.pluck(:mood)).to eq([ 'dark' ])
|
|
2030
|
+
end
|
|
2031
|
+
end
|
|
2032
|
+
|
|
2033
|
+
context 'when the field name is a method name' do
|
|
2034
|
+
let!(:person) do
|
|
2035
|
+
Person.create!(ssn: 'secret-ssn')
|
|
2036
|
+
end
|
|
2037
|
+
|
|
2038
|
+
let!(:address) do
|
|
2039
|
+
person.addresses.create!(street: 'hobrecht')
|
|
2040
|
+
end
|
|
2041
|
+
|
|
2042
|
+
let(:criteria) do
|
|
2043
|
+
Address.all.tap do |crit|
|
|
2044
|
+
crit.documents = [ address ]
|
|
2045
|
+
end
|
|
2046
|
+
end
|
|
2047
|
+
|
|
2048
|
+
it 'does not call the method' do
|
|
2049
|
+
expect(address).not_to receive(:destroy)
|
|
2050
|
+
expect(context.pluck(:destroy)).to eq([ nil ])
|
|
2051
|
+
end
|
|
2052
|
+
|
|
2053
|
+
it 'does not return the attributes hash' do
|
|
2054
|
+
expect(context.pluck(:attributes)).to eq([ nil ])
|
|
2055
|
+
end
|
|
2056
|
+
|
|
2057
|
+
it 'does not traverse to the parent document' do
|
|
2058
|
+
expect(context.pluck('_root.ssn')).to eq([ nil ])
|
|
2059
|
+
end
|
|
2060
|
+
|
|
2061
|
+
it 'does not destroy the parent document' do
|
|
2062
|
+
expect(context.pluck('_root.destroy')).to eq([ nil ])
|
|
2063
|
+
expect(Person.where(_id: person.id).count).to eq(1)
|
|
2064
|
+
end
|
|
2065
|
+
end
|
|
2066
|
+
|
|
2067
|
+
context 'when plucking a belongs_to association' do
|
|
2068
|
+
let!(:band) do
|
|
2069
|
+
Band.create!(name: 'Depeche Mode')
|
|
2070
|
+
end
|
|
2071
|
+
|
|
2072
|
+
let!(:artist) do
|
|
2073
|
+
Artist.create!(band: band)
|
|
2074
|
+
end
|
|
2075
|
+
|
|
2076
|
+
let(:criteria) do
|
|
2077
|
+
Artist.all.tap do |crit|
|
|
2078
|
+
crit.documents = [ artist ]
|
|
2079
|
+
end
|
|
2080
|
+
end
|
|
2081
|
+
|
|
2082
|
+
it 'traverses to the associated document' do
|
|
2083
|
+
expect(context.pluck('band.name')).to eq([ 'Depeche Mode' ])
|
|
2084
|
+
end
|
|
2085
|
+
|
|
2086
|
+
it 'returns the document for the association name' do
|
|
2087
|
+
expect(context.pluck(:band)).to eq([ band ])
|
|
2088
|
+
end
|
|
2089
|
+
|
|
2090
|
+
it 'returns the id for the foreign key' do
|
|
2091
|
+
expect(context.pluck(:band_id)).to eq([ band.id ])
|
|
2092
|
+
end
|
|
2093
|
+
end
|
|
2094
|
+
|
|
2095
|
+
context 'when plucking the foreign key of a many to many association' do
|
|
2096
|
+
let!(:preference) do
|
|
2097
|
+
Preference.create!(name: 'nature')
|
|
2098
|
+
end
|
|
2099
|
+
|
|
2100
|
+
let!(:person) do
|
|
2101
|
+
Person.create!(preferences: [ preference ])
|
|
2102
|
+
end
|
|
2103
|
+
|
|
2104
|
+
let(:criteria) do
|
|
2105
|
+
Person.all.tap do |crit|
|
|
2106
|
+
crit.documents = [ person ]
|
|
2107
|
+
end
|
|
2108
|
+
end
|
|
2109
|
+
|
|
2110
|
+
it 'returns the stored ids' do
|
|
2111
|
+
expect(context.pluck(:preference_ids)).to eq([ [ preference.id ] ])
|
|
2112
|
+
end
|
|
2113
|
+
end
|
|
2114
|
+
|
|
2115
|
+
context 'when the field path has an empty segment' do
|
|
2116
|
+
let!(:band) do
|
|
2117
|
+
Band.create!(name: 'Depeche Mode', label: Label.new(name: 'Mute'))
|
|
2118
|
+
end
|
|
2119
|
+
|
|
2120
|
+
let(:criteria) do
|
|
2121
|
+
Band.all.tap do |crit|
|
|
2122
|
+
crit.documents = [ band ]
|
|
2123
|
+
end
|
|
2124
|
+
end
|
|
2125
|
+
|
|
2126
|
+
it 'returns nil' do
|
|
2127
|
+
expect(context.pluck('label..name')).to eq([ nil ])
|
|
2128
|
+
end
|
|
2129
|
+
end
|
|
1999
2130
|
end
|
|
2000
2131
|
|
|
2001
2132
|
describe "#pick" do
|
|
@@ -2056,6 +2187,27 @@ describe Mongoid::Contextual::Memory do
|
|
|
2056
2187
|
expect(picked).to be_nil
|
|
2057
2188
|
end
|
|
2058
2189
|
end
|
|
2190
|
+
|
|
2191
|
+
context 'when the field name is a method name' do
|
|
2192
|
+
let!(:person) do
|
|
2193
|
+
Person.create!(ssn: 'secret-ssn')
|
|
2194
|
+
end
|
|
2195
|
+
|
|
2196
|
+
let!(:address) do
|
|
2197
|
+
person.addresses.create!(street: 'hobrecht')
|
|
2198
|
+
end
|
|
2199
|
+
|
|
2200
|
+
let(:criteria) do
|
|
2201
|
+
Address.all.tap do |crit|
|
|
2202
|
+
crit.documents = [ address ]
|
|
2203
|
+
end
|
|
2204
|
+
end
|
|
2205
|
+
|
|
2206
|
+
it 'does not call the method' do
|
|
2207
|
+
expect(address).not_to receive(:destroy)
|
|
2208
|
+
expect(context.pick(:destroy)).to be_nil
|
|
2209
|
+
end
|
|
2210
|
+
end
|
|
2059
2211
|
end
|
|
2060
2212
|
|
|
2061
2213
|
describe "#tally" do
|
|
@@ -2510,6 +2662,31 @@ describe Mongoid::Contextual::Memory do
|
|
|
2510
2662
|
)
|
|
2511
2663
|
end
|
|
2512
2664
|
end
|
|
2665
|
+
|
|
2666
|
+
context 'when the field name is a method name' do
|
|
2667
|
+
let!(:person) do
|
|
2668
|
+
Person.create!(ssn: 'secret-ssn')
|
|
2669
|
+
end
|
|
2670
|
+
|
|
2671
|
+
let!(:address) do
|
|
2672
|
+
person.addresses.create!(street: 'hobrecht')
|
|
2673
|
+
end
|
|
2674
|
+
|
|
2675
|
+
let(:criteria) do
|
|
2676
|
+
Address.all.tap do |crit|
|
|
2677
|
+
crit.documents = [ address ]
|
|
2678
|
+
end
|
|
2679
|
+
end
|
|
2680
|
+
|
|
2681
|
+
it 'does not call the method' do
|
|
2682
|
+
expect(address).not_to receive(:destroy)
|
|
2683
|
+
expect(context.tally(:destroy)).to eq(nil => 1)
|
|
2684
|
+
end
|
|
2685
|
+
|
|
2686
|
+
it 'does not disclose the parent document fields' do
|
|
2687
|
+
expect(context.tally('_root.ssn')).to eq(nil => 1)
|
|
2688
|
+
end
|
|
2689
|
+
end
|
|
2513
2690
|
end
|
|
2514
2691
|
|
|
2515
2692
|
describe '#inc' do
|
|
@@ -1829,6 +1829,8 @@ describe Mongoid::Criteria::Queryable::Selectable do
|
|
|
1829
1829
|
end
|
|
1830
1830
|
|
|
1831
1831
|
context 'when the following criteria uses string were form' do
|
|
1832
|
+
# String criteria compile to $where, which requires the opt-in.
|
|
1833
|
+
config_override :allow_unsafe_query_operators, true
|
|
1832
1834
|
|
|
1833
1835
|
let(:selection) do
|
|
1834
1836
|
query.not.where('hello world')
|