mongoid 9.0.11 → 9.0.12
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 +45 -0
- data/lib/mongoid/association/depending.rb +8 -4
- 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/collection_configurable.rb +8 -0
- data/lib/mongoid/config/encryption.rb +36 -18
- 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 +46 -0
- data/lib/mongoid/errors/in_memory_regexp_timeout.rb +26 -0
- data/lib/mongoid/errors/no_encryption_schema.rb +28 -0
- data/lib/mongoid/errors.rb +2 -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 +35 -0
- data/lib/mongoid/search_indexable.rb +14 -7
- data/lib/mongoid/tasks/database.rb +23 -9
- data/lib/mongoid/threaded.rb +37 -0
- data/lib/mongoid/version.rb +1 -1
- data/spec/integration/app_spec.rb +7 -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/encryption_spec.rb +149 -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/config/defaults_spec.rb +18 -0
- data/spec/mongoid/config/encryption_spec.rb +228 -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 +61 -0
- data/spec/mongoid/matcher/regexp_budget_spec.rb +570 -0
- data/spec/mongoid/tasks/database_spec.rb +18 -0
- data/spec/support/crypt/models.rb +157 -0
- metadata +14 -2
|
@@ -1832,6 +1832,151 @@ describe Mongoid::Association::Referenced::HasMany::Proxy do
|
|
|
1832
1832
|
expect(person.posts).to eq([])
|
|
1833
1833
|
end
|
|
1834
1834
|
end
|
|
1835
|
+
|
|
1836
|
+
context 'when a document is appended with an id that is already loaded' do
|
|
1837
|
+
# Iterating the association moves an appended document out of _added
|
|
1838
|
+
# without adding it to _loaded, so the scan for matches and the
|
|
1839
|
+
# removal that follows see different instances for the same id.
|
|
1840
|
+
let(:person) { Person.create! }
|
|
1841
|
+
let!(:post) { person.posts.create!(title: 'Testing') }
|
|
1842
|
+
let(:reloaded) { Person.find(person._id) }
|
|
1843
|
+
|
|
1844
|
+
before do
|
|
1845
|
+
reloaded.posts.to_a
|
|
1846
|
+
reloaded.posts._target.push(
|
|
1847
|
+
Post.new(_id: post._id, title: 'Testing', person_id: reloaded._id)
|
|
1848
|
+
)
|
|
1849
|
+
end
|
|
1850
|
+
|
|
1851
|
+
shared_examples 'removes the document once' do
|
|
1852
|
+
it 'sets the association locally' do
|
|
1853
|
+
reloaded.posts.send(method, conditions)
|
|
1854
|
+
expect(reloaded.posts).to eq([])
|
|
1855
|
+
end
|
|
1856
|
+
|
|
1857
|
+
it 'deletes the documents from the database' do
|
|
1858
|
+
reloaded.posts.send(method, conditions)
|
|
1859
|
+
expect(Post.count).to eq(0)
|
|
1860
|
+
end
|
|
1861
|
+
end
|
|
1862
|
+
|
|
1863
|
+
context 'when the conditions carry no regular expression' do
|
|
1864
|
+
let(:conditions) { { title: 'Testing' } }
|
|
1865
|
+
|
|
1866
|
+
it_behaves_like 'removes the document once'
|
|
1867
|
+
end
|
|
1868
|
+
|
|
1869
|
+
context 'when the conditions carry a regular expression' do
|
|
1870
|
+
let(:conditions) { { title: /Testing/ } }
|
|
1871
|
+
|
|
1872
|
+
it_behaves_like 'removes the document once'
|
|
1873
|
+
end
|
|
1874
|
+
end
|
|
1875
|
+
|
|
1876
|
+
context 'when an appended document disagrees with the loaded one' do
|
|
1877
|
+
# Only the appended instance matches, so the removal has to drop that
|
|
1878
|
+
# id from both _loaded and _added. Removing by id and stopping at the
|
|
1879
|
+
# first hash that has it would take the loaded instance instead and
|
|
1880
|
+
# leave the unbound one behind.
|
|
1881
|
+
let(:person) { Person.create! }
|
|
1882
|
+
let!(:post) { person.posts.create!(title: 'Other') }
|
|
1883
|
+
let(:reloaded) { Person.find(person._id) }
|
|
1884
|
+
|
|
1885
|
+
before do
|
|
1886
|
+
reloaded.posts.to_a
|
|
1887
|
+
reloaded.posts._target.push(
|
|
1888
|
+
Post.new(_id: post._id, title: 'Testing', person_id: reloaded._id)
|
|
1889
|
+
)
|
|
1890
|
+
end
|
|
1891
|
+
|
|
1892
|
+
it 'deletes nothing from the database' do
|
|
1893
|
+
expect(reloaded.posts.send(method, { title: /Testing/ })).to eq(0)
|
|
1894
|
+
expect(Post.where(title: 'Other').count).to eq(1)
|
|
1895
|
+
end
|
|
1896
|
+
|
|
1897
|
+
it 'leaves no unbound document in the association' do
|
|
1898
|
+
reloaded.posts.send(method, { title: /Testing/ })
|
|
1899
|
+
expect(reloaded.posts._target.in_memory.map(&:person_id)).not_to include(nil)
|
|
1900
|
+
end
|
|
1901
|
+
end
|
|
1902
|
+
|
|
1903
|
+
context 'when a nested selector is evaluated while the association loads' do
|
|
1904
|
+
# Loading the association runs find callbacks, and application code in
|
|
1905
|
+
# one of those can evaluate a selector of its own. A scope opened
|
|
1906
|
+
# around the load would mark it as having nothing to bound, and that
|
|
1907
|
+
# suppresses the decision for the nested selector too, leaving any
|
|
1908
|
+
# pattern in it unguarded.
|
|
1909
|
+
config_override :in_memory_regexp_time_limit, 5.0
|
|
1910
|
+
|
|
1911
|
+
let(:person) { Person.create! }
|
|
1912
|
+
let(:reloaded) { Person.find(person._id) }
|
|
1913
|
+
|
|
1914
|
+
before do
|
|
1915
|
+
person.posts.create!(title: 'Testing')
|
|
1916
|
+
person.posts.create!(title: 'Keep')
|
|
1917
|
+
reloaded
|
|
1918
|
+
end
|
|
1919
|
+
|
|
1920
|
+
it 'leaves the nested scope free to establish its own budget' do
|
|
1921
|
+
seen = []
|
|
1922
|
+
allow(Mongoid::Factory).to receive(:from_db).and_wrap_original do |original, *args|
|
|
1923
|
+
Mongoid::Matcher::RegexpBudget.open('title' => /Testing/) do
|
|
1924
|
+
seen << Mongoid::Matcher::RegexpBudget.remaining
|
|
1925
|
+
end
|
|
1926
|
+
original.call(*args)
|
|
1927
|
+
end
|
|
1928
|
+
|
|
1929
|
+
reloaded.posts.send(method, { title: 'Testing' })
|
|
1930
|
+
|
|
1931
|
+
expect(seen).not_to be_empty
|
|
1932
|
+
expect(seen).not_to include(nil)
|
|
1933
|
+
end
|
|
1934
|
+
end
|
|
1935
|
+
|
|
1936
|
+
if method == :delete_all
|
|
1937
|
+
context 'when the association has not been loaded' do
|
|
1938
|
+
let(:person) { Person.create! }
|
|
1939
|
+
let(:reloaded) { Person.find(person._id) }
|
|
1940
|
+
let(:subscriber) { Mrss::EventSubscriber.new }
|
|
1941
|
+
|
|
1942
|
+
before do
|
|
1943
|
+
person.posts.create!(title: 'Testing')
|
|
1944
|
+
person.posts.create!(title: 'Test')
|
|
1945
|
+
reloaded
|
|
1946
|
+
end
|
|
1947
|
+
|
|
1948
|
+
def commands_for(conditions)
|
|
1949
|
+
Person.collection.client.subscribe(Mongo::Monitoring::COMMAND, subscriber)
|
|
1950
|
+
reloaded.posts.delete_all(conditions)
|
|
1951
|
+
subscriber.started_events.map(&:command_name)
|
|
1952
|
+
ensure
|
|
1953
|
+
Person.collection.client.unsubscribe(Mongo::Monitoring::COMMAND, subscriber)
|
|
1954
|
+
end
|
|
1955
|
+
|
|
1956
|
+
context 'when the conditions carry no regular expression' do
|
|
1957
|
+
# Nothing to bound, so the scan for matches stays where it was,
|
|
1958
|
+
# after the delete, where the association it loads comes back
|
|
1959
|
+
# empty. Moving it ahead of the delete would transfer the whole
|
|
1960
|
+
# association for an operation that need send nothing over the
|
|
1961
|
+
# wire.
|
|
1962
|
+
it 'deletes before loading the association' do
|
|
1963
|
+
expect(commands_for(nil)).to eq(%w[ delete find ])
|
|
1964
|
+
end
|
|
1965
|
+
end
|
|
1966
|
+
|
|
1967
|
+
context 'when the conditions carry a regular expression' do
|
|
1968
|
+
# The scan has to run under a budget, and before anything is
|
|
1969
|
+
# deleted, so that a budget which runs out leaves the database
|
|
1970
|
+
# untouched. What it scans is what is already in memory: loading
|
|
1971
|
+
# would trade the matching cost the budget exists to bound for an
|
|
1972
|
+
# unbounded amount of memory, since /.*/ is both cheap to supply
|
|
1973
|
+
# and matches every document in the association.
|
|
1974
|
+
it 'deletes without loading the association' do
|
|
1975
|
+
expect(commands_for({ title: /Test/ })).to eq(%w[ delete ])
|
|
1976
|
+
end
|
|
1977
|
+
end
|
|
1978
|
+
end
|
|
1979
|
+
end
|
|
1835
1980
|
end
|
|
1836
1981
|
|
|
1837
1982
|
context 'when the association is polymorphic' do
|
|
@@ -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
|
|
@@ -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
|
|