mongoid 9.0.11 → 9.0.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 +65 -0
- data/lib/mongoid/association/depending.rb +8 -4
- data/lib/mongoid/association/embedded/batchable.rb +8 -0
- data/lib/mongoid/association/nested/many.rb +34 -5
- data/lib/mongoid/association/nested/nested_buildable.rb +14 -0
- data/lib/mongoid/association/referenced/has_many/proxy.rb +100 -1
- data/lib/mongoid/association/relatable.rb +17 -0
- data/lib/mongoid/clients/factory.rb +2 -0
- data/lib/mongoid/collection_configurable.rb +8 -0
- data/lib/mongoid/config/encryption.rb +171 -22
- data/lib/mongoid/config.rb +56 -9
- data/lib/mongoid/contextual/aggregable/memory.rb +5 -2
- data/lib/mongoid/contextual/memory.rb +18 -7
- data/lib/mongoid/criteria/queryable/mergeable.rb +4 -0
- data/lib/mongoid/criteria/queryable/selectable.rb +109 -0
- data/lib/mongoid/encryptable.rb +73 -0
- data/lib/mongoid/errors/in_memory_regexp_timeout.rb +26 -0
- data/lib/mongoid/errors/invalid_encryption_schema.rb +27 -0
- data/lib/mongoid/errors/no_encryption_schema.rb +30 -0
- data/lib/mongoid/errors.rb +3 -0
- data/lib/mongoid/field_readable.rb +70 -0
- data/lib/mongoid/indexable.rb +39 -27
- data/lib/mongoid/matchable.rb +6 -1
- data/lib/mongoid/matcher/eq_impl_with_regexp.rb +4 -9
- data/lib/mongoid/matcher/regex.rb +9 -13
- data/lib/mongoid/matcher/regexp_budget.rb +383 -0
- data/lib/mongoid/matcher.rb +1 -0
- data/lib/mongoid/persistence_context.rb +77 -0
- data/lib/mongoid/search_indexable.rb +19 -8
- data/lib/mongoid/tasks/database.rb +33 -16
- data/lib/mongoid/threaded.rb +37 -0
- data/lib/mongoid/version.rb +1 -1
- data/lib/mongoid/warnings.rb +4 -0
- data/spec/integration/app_spec.rb +7 -0
- data/spec/integration/associations/has_and_belongs_to_many_spec.rb +17 -2
- data/spec/integration/atomic/modifiers_spec.rb +57 -0
- data/spec/integration/dots_and_dollars_spec.rb +12 -2
- data/spec/integration/encryption_spec.rb +298 -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 +204 -10
- data/spec/mongoid/clients/factory_spec.rb +36 -0
- data/spec/mongoid/config/defaults_spec.rb +18 -0
- data/spec/mongoid/config/encryption_spec.rb +451 -0
- 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 +200 -0
- data/spec/mongoid/criteria_spec.rb +2 -0
- data/spec/mongoid/encryptable_spec.rb +115 -0
- data/spec/mongoid/matcher/regexp_budget_spec.rb +576 -0
- data/spec/mongoid/search_indexable_spec.rb +34 -1
- data/spec/mongoid/tasks/database_spec.rb +31 -0
- data/spec/support/crypt/models.rb +253 -0
- data/spec/support/models/modifier_models.rb +32 -0
- metadata +17 -2
|
@@ -8,11 +8,38 @@ require_relative '../association/referenced/has_and_belongs_to_many_models'
|
|
|
8
8
|
require_relative './nested_spec_models'
|
|
9
9
|
|
|
10
10
|
describe Mongoid::Attributes::Nested do
|
|
11
|
+
# Expects the enclosing context to define `build_with_id`, taking an id
|
|
12
|
+
# value and returning the document built with it in nested attributes.
|
|
13
|
+
shared_examples 'rejects non-scalar ids' do |klass_name|
|
|
14
|
+
not_found = /not found for class #{klass_name}/
|
|
15
|
+
|
|
16
|
+
context 'when the id is an operator hash' do
|
|
17
|
+
it 'raises a document not found error' do
|
|
18
|
+
expect { build_with_id({ '$ne' => nil }) }
|
|
19
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound, not_found)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
context 'when the id is an array' do
|
|
24
|
+
it 'raises a document not found error' do
|
|
25
|
+
expect { build_with_id([ BSON::ObjectId.new ]) }
|
|
26
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound, not_found)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
context 'when the id is a malformed object id hash' do
|
|
31
|
+
it 'raises a document not found error' do
|
|
32
|
+
expect { build_with_id({ '$oid' => 'junk' }) }
|
|
33
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound, not_found)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
11
37
|
|
|
12
38
|
describe ".accepts_nested_attributes_for" do
|
|
13
39
|
|
|
14
40
|
context "when the autosave option is not defined" do
|
|
15
41
|
|
|
42
|
+
|
|
16
43
|
let(:person) do
|
|
17
44
|
Person.new
|
|
18
45
|
end
|
|
@@ -202,6 +229,30 @@ describe Mongoid::Attributes::Nested do
|
|
|
202
229
|
end
|
|
203
230
|
end
|
|
204
231
|
end
|
|
232
|
+
|
|
233
|
+
context 'when the id in the attributes is not a scalar' do
|
|
234
|
+
let(:person) { Person.create! }
|
|
235
|
+
|
|
236
|
+
def build_with_id(id)
|
|
237
|
+
person.update!(posts_attributes: { '0' => { id: id, title: 'Crafted' } })
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
context 'when the association is empty' do
|
|
241
|
+
include_examples 'rejects non-scalar ids', 'Post'
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
context 'when the association is not empty' do
|
|
245
|
+
before { person.posts.create!(title: 'Existing') }
|
|
246
|
+
|
|
247
|
+
include_examples 'rejects non-scalar ids', 'Post'
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
context 'when allow_reparenting_via_nested_attributes is true' do
|
|
251
|
+
config_override :allow_reparenting_via_nested_attributes, true
|
|
252
|
+
|
|
253
|
+
include_examples 'rejects non-scalar ids', 'Post'
|
|
254
|
+
end
|
|
255
|
+
end
|
|
205
256
|
end
|
|
206
257
|
|
|
207
258
|
context 'when the relation is a has-and-belongs-to-many' do
|
|
@@ -226,16 +277,100 @@ describe Mongoid::Attributes::Nested do
|
|
|
226
277
|
)
|
|
227
278
|
end
|
|
228
279
|
|
|
229
|
-
|
|
230
|
-
|
|
280
|
+
context 'when allow_reparenting_via_nested_attributes is false' do
|
|
281
|
+
config_override :allow_reparenting_via_nested_attributes, false
|
|
282
|
+
|
|
283
|
+
it 'raises a document not found error' do
|
|
284
|
+
expect { person }.to raise_error(Mongoid::Errors::DocumentNotFound,
|
|
285
|
+
/Document\(s\) not found for class Preference/)
|
|
286
|
+
end
|
|
231
287
|
end
|
|
232
288
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
289
|
+
context 'when allow_reparenting_via_nested_attributes is true' do
|
|
290
|
+
config_override :allow_reparenting_via_nested_attributes, true
|
|
291
|
+
|
|
292
|
+
it 'sets the nested attributes' do
|
|
293
|
+
expect(person.preferences.map(&:name)).to eq([ preference.name ])
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
it 'updates attributes of existing document which is added to relation' do
|
|
297
|
+
preference_name = 'updated preference'
|
|
298
|
+
person = Person.new(
|
|
299
|
+
preferences_attributes: { 0 => { id: preference.id, name: preference_name } }
|
|
300
|
+
)
|
|
301
|
+
expect(person.preferences.map(&:name)).to eq([ preference_name ])
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
context 'when the id does not correspond to an existing document' do
|
|
305
|
+
let(:person) do
|
|
306
|
+
Person.new(
|
|
307
|
+
preferences_attributes: { 0 => { id: BSON::ObjectId.new, name: 'Ghost' } }
|
|
308
|
+
)
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
context 'when raise_not_found_error is true' do
|
|
312
|
+
config_override :raise_not_found_error, true
|
|
313
|
+
|
|
314
|
+
it 'raises a document not found error' do
|
|
315
|
+
expect { person }.to raise_error(Mongoid::Errors::DocumentNotFound,
|
|
316
|
+
/Document\(s\) not found for class Preference/)
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
context 'when raise_not_found_error is false' do
|
|
321
|
+
config_override :raise_not_found_error, false
|
|
322
|
+
|
|
323
|
+
it 'raises a document not found error' do
|
|
324
|
+
expect { person }.to raise_error(Mongoid::Errors::DocumentNotFound,
|
|
325
|
+
/Document\(s\) not found for class Preference/)
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
context 'when _destroy is true for a document not in the relation' do
|
|
332
|
+
before do
|
|
333
|
+
Person.send(:undef_method, :preferences_attributes=)
|
|
334
|
+
Person.accepts_nested_attributes_for :preferences, allow_destroy: true
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
let(:person) do
|
|
338
|
+
Person.new(
|
|
339
|
+
preferences_attributes: { 0 => { id: preference.id, _destroy: '1' } }
|
|
340
|
+
)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
it 'does not raise UnknownAttribute' do
|
|
344
|
+
expect { person }.not_to raise_error
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
it 'does not add the document to the relation' do
|
|
348
|
+
expect(person.preferences).to be_empty
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
context 'when the id in the attributes is not a scalar' do
|
|
354
|
+
let(:target) { Person.create! }
|
|
355
|
+
|
|
356
|
+
def build_with_id(id)
|
|
357
|
+
target.update!(preferences_attributes: { 0 => { id: id, name: 'Crafted' } })
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
context 'when the association is empty' do
|
|
361
|
+
include_examples 'rejects non-scalar ids', 'Preference'
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
context 'when the association is not empty' do
|
|
365
|
+
before { target.preferences.create!(name: 'Existing') }
|
|
366
|
+
|
|
367
|
+
include_examples 'rejects non-scalar ids', 'Preference'
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
context 'when allow_reparenting_via_nested_attributes is true' do
|
|
371
|
+
config_override :allow_reparenting_via_nested_attributes, true
|
|
372
|
+
|
|
373
|
+
include_examples 'rejects non-scalar ids', 'Preference'
|
|
239
374
|
end
|
|
240
375
|
end
|
|
241
376
|
end
|
|
@@ -3041,7 +3176,7 @@ describe Mongoid::Attributes::Nested do
|
|
|
3041
3176
|
{ '0' =>
|
|
3042
3177
|
{ 'id' => BSON::ObjectId.new.to_s, 'title' => 'Rogue' } }
|
|
3043
3178
|
end.to raise_error(Mongoid::Errors::DocumentNotFound,
|
|
3044
|
-
/Document
|
|
3179
|
+
/Document not found for class Post/)
|
|
3045
3180
|
end
|
|
3046
3181
|
end
|
|
3047
3182
|
end
|
|
@@ -3076,7 +3211,7 @@ describe Mongoid::Attributes::Nested do
|
|
|
3076
3211
|
person.posts_attributes =
|
|
3077
3212
|
{ 'foo' => { 'id' => 'test', 'title' => 'Test' } }
|
|
3078
3213
|
end.to raise_error(Mongoid::Errors::DocumentNotFound,
|
|
3079
|
-
/Document
|
|
3214
|
+
/Document not found for class Post/)
|
|
3080
3215
|
end
|
|
3081
3216
|
end
|
|
3082
3217
|
end
|
|
@@ -5025,4 +5160,63 @@ describe Mongoid::Attributes::Nested do
|
|
|
5025
5160
|
end
|
|
5026
5161
|
end
|
|
5027
5162
|
end
|
|
5163
|
+
|
|
5164
|
+
context 'when an id in nested attributes belongs to another parent' do
|
|
5165
|
+
let(:victim) { Person.create! }
|
|
5166
|
+
let(:attacker) { Person.create! }
|
|
5167
|
+
|
|
5168
|
+
context 'when the association is a has-many' do
|
|
5169
|
+
before do
|
|
5170
|
+
Person.send(:undef_method, :posts_attributes=)
|
|
5171
|
+
Person.accepts_nested_attributes_for :posts
|
|
5172
|
+
end
|
|
5173
|
+
|
|
5174
|
+
let!(:victim_post) { victim.posts.create!(title: 'Private') }
|
|
5175
|
+
|
|
5176
|
+
it 'does not update the other parent\'s document' do
|
|
5177
|
+
expect { attacker.update!(posts_attributes: { '0' => { id: victim_post.id, title: 'Overwritten' } }) }
|
|
5178
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound)
|
|
5179
|
+
expect(victim_post.reload.title).to eq('Private')
|
|
5180
|
+
end
|
|
5181
|
+
|
|
5182
|
+
it 'does not add the other parent\'s document to the association' do
|
|
5183
|
+
expect { attacker.update!(posts_attributes: { '0' => { id: victim_post.id, title: 'Overwritten' } }) }
|
|
5184
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound)
|
|
5185
|
+
expect(attacker.reload.posts).to be_empty
|
|
5186
|
+
end
|
|
5187
|
+
|
|
5188
|
+
it 'does not update via a direct association assignment' do
|
|
5189
|
+
expect { attacker.update!(posts: { '0' => { id: victim_post.id, title: 'Overwritten' } }) }
|
|
5190
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound)
|
|
5191
|
+
expect(victim_post.reload.title).to eq('Private')
|
|
5192
|
+
end
|
|
5193
|
+
end
|
|
5194
|
+
|
|
5195
|
+
context 'when the association is a has-and-belongs-to-many' do
|
|
5196
|
+
before do
|
|
5197
|
+
Person.send(:undef_method, :preferences_attributes=)
|
|
5198
|
+
Person.accepts_nested_attributes_for :preferences
|
|
5199
|
+
end
|
|
5200
|
+
|
|
5201
|
+
let!(:victim_preference) { victim.preferences.create!(name: 'Private') }
|
|
5202
|
+
|
|
5203
|
+
it 'does not update the other parent\'s document' do
|
|
5204
|
+
expect { attacker.update!(preferences_attributes: { '0' => { id: victim_preference.id, name: 'Overwritten' } }) }
|
|
5205
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound)
|
|
5206
|
+
expect(victim_preference.reload.name).to eq('Private')
|
|
5207
|
+
end
|
|
5208
|
+
|
|
5209
|
+
it 'does not add the other parent\'s document to the association' do
|
|
5210
|
+
expect { attacker.update!(preferences_attributes: { '0' => { id: victim_preference.id, name: 'Overwritten' } }) }
|
|
5211
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound)
|
|
5212
|
+
expect(attacker.reload.preferences).to be_empty
|
|
5213
|
+
end
|
|
5214
|
+
|
|
5215
|
+
it 'does not update via a direct association assignment' do
|
|
5216
|
+
expect { attacker.update!(preferences: { '0' => { id: victim_preference.id, name: 'Overwritten' } }) }
|
|
5217
|
+
.to raise_error(Mongoid::Errors::DocumentNotFound)
|
|
5218
|
+
expect(victim_preference.reload.name).to eq('Private')
|
|
5219
|
+
end
|
|
5220
|
+
end
|
|
5221
|
+
end
|
|
5028
5222
|
end
|
|
@@ -354,6 +354,42 @@ describe Mongoid::Clients::Factory do
|
|
|
354
354
|
end
|
|
355
355
|
end
|
|
356
356
|
end
|
|
357
|
+
|
|
358
|
+
context 'when bypass_auto_encryption is set' do
|
|
359
|
+
it 'warns that encryption is bypassed' do
|
|
360
|
+
expect(Mongoid::Warnings).to receive(:warn_bypass_auto_encryption)
|
|
361
|
+
|
|
362
|
+
described_class.send(
|
|
363
|
+
:build_auto_encryption_options,
|
|
364
|
+
{ auto_encryption_options: { bypass_auto_encryption: true, schema_map: {} } },
|
|
365
|
+
database_id
|
|
366
|
+
)
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
context 'when bypass_auto_encryption is not set' do
|
|
371
|
+
it 'does not warn' do
|
|
372
|
+
expect(Mongoid::Warnings).not_to receive(:warn_bypass_auto_encryption)
|
|
373
|
+
|
|
374
|
+
described_class.send(
|
|
375
|
+
:build_auto_encryption_options,
|
|
376
|
+
{ auto_encryption_options: { schema_map: {} } },
|
|
377
|
+
database_id
|
|
378
|
+
)
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
context 'when bypass_auto_encryption is false' do
|
|
383
|
+
it 'does not warn' do
|
|
384
|
+
expect(Mongoid::Warnings).not_to receive(:warn_bypass_auto_encryption)
|
|
385
|
+
|
|
386
|
+
described_class.send(
|
|
387
|
+
:build_auto_encryption_options,
|
|
388
|
+
{ auto_encryption_options: { bypass_auto_encryption: false, schema_map: {} } },
|
|
389
|
+
database_id
|
|
390
|
+
)
|
|
391
|
+
end
|
|
392
|
+
end
|
|
357
393
|
end
|
|
358
394
|
|
|
359
395
|
context "when no name is provided" do
|
|
@@ -41,6 +41,18 @@ describe Mongoid::Config::Defaults do
|
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
+
shared_examples 'uses settings for 9.0' do
|
|
45
|
+
it 'uses settings for 9.0' do
|
|
46
|
+
expect(Mongoid.autosave_saves_unchanged_documents).to be true
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
shared_examples 'never allows reparenting via nested attributes' do
|
|
51
|
+
it 'leaves allow_reparenting_via_nested_attributes false' do
|
|
52
|
+
expect(Mongoid.allow_reparenting_via_nested_attributes).to be false
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
44
56
|
context "when giving a valid version" do
|
|
45
57
|
|
|
46
58
|
before do
|
|
@@ -57,6 +69,8 @@ describe Mongoid::Config::Defaults do
|
|
|
57
69
|
|
|
58
70
|
it_behaves_like "uses settings for 8.0"
|
|
59
71
|
it_behaves_like "uses settings for 8.1"
|
|
72
|
+
it_behaves_like 'uses settings for 9.0'
|
|
73
|
+
it_behaves_like 'never allows reparenting via nested attributes'
|
|
60
74
|
end
|
|
61
75
|
|
|
62
76
|
context "when the given version is 8.1" do
|
|
@@ -65,6 +79,8 @@ describe Mongoid::Config::Defaults do
|
|
|
65
79
|
|
|
66
80
|
it_behaves_like "does not use settings for 8.0"
|
|
67
81
|
it_behaves_like "uses settings for 8.1"
|
|
82
|
+
it_behaves_like 'uses settings for 9.0'
|
|
83
|
+
it_behaves_like 'never allows reparenting via nested attributes'
|
|
68
84
|
end
|
|
69
85
|
|
|
70
86
|
context "when the given version is 9.0" do
|
|
@@ -73,6 +89,8 @@ describe Mongoid::Config::Defaults do
|
|
|
73
89
|
|
|
74
90
|
it_behaves_like "does not use settings for 8.0"
|
|
75
91
|
it_behaves_like "does not use settings for 8.1"
|
|
92
|
+
it_behaves_like 'uses settings for 9.0'
|
|
93
|
+
it_behaves_like 'never allows reparenting via nested attributes'
|
|
76
94
|
end
|
|
77
95
|
end
|
|
78
96
|
|