mongoid 9.0.7 → 9.0.9
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/mongoid/association/embedded/batchable.rb +11 -10
- data/lib/mongoid/association/embedded/embeds_many/proxy.rb +64 -29
- data/lib/mongoid/association/nested/many.rb +2 -0
- data/lib/mongoid/association/nested/one.rb +1 -1
- data/lib/mongoid/association/referenced/has_and_belongs_to_many/proxy.rb +0 -6
- data/lib/mongoid/association/referenced/has_many/enumerable.rb +40 -0
- data/lib/mongoid/association/referenced/has_many/proxy.rb +17 -5
- data/lib/mongoid/attributes.rb +19 -1
- data/lib/mongoid/changeable.rb +10 -1
- data/lib/mongoid/clients/sessions.rb +3 -4
- data/lib/mongoid/config.rb +1 -1
- data/lib/mongoid/contextual/aggregable/mongo.rb +6 -1
- data/lib/mongoid/contextual/mongo.rb +8 -89
- data/lib/mongoid/pluckable.rb +132 -0
- data/lib/mongoid/railties/bson_object_id_serializer.rb +7 -0
- data/lib/mongoid/reloadable.rb +6 -0
- data/lib/mongoid/traversable.rb +0 -2
- data/lib/mongoid/version.rb +1 -1
- data/spec/integration/associations/embeds_many_spec.rb +110 -0
- data/spec/integration/associations/has_and_belongs_to_many_spec.rb +81 -0
- data/spec/integration/associations/has_many_spec.rb +56 -0
- data/spec/integration/associations/has_one_spec.rb +55 -3
- data/spec/mongoid/association/referenced/has_many/enumerable_spec.rb +394 -0
- data/spec/mongoid/association/referenced/has_many_models.rb +24 -0
- data/spec/mongoid/association/referenced/has_one_models.rb +10 -2
- data/spec/mongoid/attributes_spec.rb +13 -0
- data/spec/mongoid/clients/transactions_spec.rb +162 -1
- data/spec/mongoid/clients/transactions_spec_models.rb +93 -0
- data/spec/mongoid/contextual/aggregable/mongo_spec.rb +33 -0
- data/spec/mongoid/reloadable_spec.rb +24 -0
- data/spec/shared/CANDIDATE.md +28 -0
- data/spec/shared/lib/mrss/spec_organizer.rb +32 -3
- data/spec/shared/shlib/server.sh +1 -1
- data/spec/support/models/company.rb +2 -0
- data/spec/support/models/passport.rb +1 -0
- data/spec/support/models/product.rb +2 -0
- data/spec/support/models/seo.rb +2 -0
- metadata +7 -4
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mongoid
|
|
4
|
+
# Provides shared behavior for any document with "pluck" functionality.
|
|
5
|
+
#
|
|
6
|
+
# @api private
|
|
7
|
+
module Pluckable
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# Prepares the field names for plucking by normalizing them to their
|
|
13
|
+
# database field names. Also prepares a projection hash if requested.
|
|
14
|
+
def prepare_pluck(field_names, document_class: klass, prepare_projection: false)
|
|
15
|
+
normalized_field_names = []
|
|
16
|
+
projection = {}
|
|
17
|
+
|
|
18
|
+
field_names.each do |f|
|
|
19
|
+
db_fn = document_class.database_field_name(f)
|
|
20
|
+
normalized_field_names.push(db_fn)
|
|
21
|
+
|
|
22
|
+
next unless prepare_projection
|
|
23
|
+
|
|
24
|
+
cleaned_name = document_class.cleanse_localized_field_names(f)
|
|
25
|
+
canonical_name = document_class.database_field_name(cleaned_name)
|
|
26
|
+
projection[canonical_name] = true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
{ field_names: normalized_field_names, projection: projection }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Plucks the given field names from the given documents.
|
|
33
|
+
def pluck_from_documents(documents, field_names, document_class: klass)
|
|
34
|
+
documents.reduce([]) do |plucked, doc|
|
|
35
|
+
values = field_names.map { |name| extract_value(doc, name.to_s, document_class) }
|
|
36
|
+
plucked << ((values.size == 1) ? values.first : values)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Fetch the element from the given hash and demongoize it using the
|
|
41
|
+
# given field. If the obj is an array, map over it and call this method
|
|
42
|
+
# on all of its elements.
|
|
43
|
+
#
|
|
44
|
+
# @param [ Hash | Array<Hash> ] obj The hash or array of hashes to fetch from.
|
|
45
|
+
# @param [ String ] key The key to fetch from the hash.
|
|
46
|
+
# @param [ Field ] field The field to use for demongoization.
|
|
47
|
+
#
|
|
48
|
+
# @return [ Object ] The demongoized value.
|
|
49
|
+
def fetch_and_demongoize(obj, key, field)
|
|
50
|
+
if obj.is_a?(Array)
|
|
51
|
+
obj.map { |doc| fetch_and_demongoize(doc, key, field) }
|
|
52
|
+
else
|
|
53
|
+
value = obj.try(:fetch, key, nil)
|
|
54
|
+
field ? field.demongoize(value) : value.class.demongoize(value)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Extracts the value for the given field name from the given attribute
|
|
59
|
+
# hash.
|
|
60
|
+
#
|
|
61
|
+
# @param [ Hash ] attrs The attributes hash.
|
|
62
|
+
# @param [ String ] field_name The name of the field to extract.
|
|
63
|
+
#
|
|
64
|
+
# @return [ Object ] The value for the given field name
|
|
65
|
+
def extract_value(attrs, field_name, document_class)
|
|
66
|
+
i = 1
|
|
67
|
+
num_meths = field_name.count('.') + 1
|
|
68
|
+
curr = attrs.dup
|
|
69
|
+
|
|
70
|
+
document_class.traverse_association_tree(field_name) do |meth, obj, is_field|
|
|
71
|
+
field = obj if is_field
|
|
72
|
+
|
|
73
|
+
# use the correct document class to check for localized fields on
|
|
74
|
+
# embedded documents.
|
|
75
|
+
document_class = obj.klass if obj.respond_to?(:klass)
|
|
76
|
+
|
|
77
|
+
is_translation = false
|
|
78
|
+
# If no association or field was found, check if the meth is an
|
|
79
|
+
# _translations field.
|
|
80
|
+
if obj.nil? && (tr = meth.match(/(.*)_translations\z/)&.captures&.first)
|
|
81
|
+
is_translation = true
|
|
82
|
+
meth = document_class.database_field_name(tr)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
curr = descend(i, curr, meth, field, num_meths, is_translation)
|
|
86
|
+
|
|
87
|
+
i += 1
|
|
88
|
+
end
|
|
89
|
+
curr
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Descend one level in the attribute hash.
|
|
93
|
+
#
|
|
94
|
+
# @param [ Integer ] part The current part index.
|
|
95
|
+
# @param [ Hash | Array<Hash> ] current The current level in the attribute hash.
|
|
96
|
+
# @param [ String ] method_name The method name to descend to.
|
|
97
|
+
# @param [ Field|nil ] field The field to use for demongoization.
|
|
98
|
+
# @param [ Boolean ] is_translation Whether the method is an _translations field.
|
|
99
|
+
# @param [ Integer ] part_count The total number of parts in the field name.
|
|
100
|
+
#
|
|
101
|
+
# @return [ Object ] The value at the next level.
|
|
102
|
+
#
|
|
103
|
+
# rubocop:disable Metrics/ParameterLists
|
|
104
|
+
def descend(part, current, method_name, field, part_count, is_translation)
|
|
105
|
+
# 1. If curr is an array fetch from all elements in the array.
|
|
106
|
+
# 2. If the field is localized, and is not an _translations field
|
|
107
|
+
# (_translations fields don't show up in the fields hash).
|
|
108
|
+
# - If this is the end of the methods, return the translation for
|
|
109
|
+
# the current locale.
|
|
110
|
+
# - Otherwise, return the whole translations hash so the next method
|
|
111
|
+
# can select the language it wants.
|
|
112
|
+
# 3. If the meth is an _translations field, do not demongoize the
|
|
113
|
+
# value so the full hash is returned.
|
|
114
|
+
# 4. Otherwise, fetch and demongoize the value for the key meth.
|
|
115
|
+
if current.is_a? Array
|
|
116
|
+
res = fetch_and_demongoize(current, method_name, field)
|
|
117
|
+
res.empty? ? nil : res
|
|
118
|
+
elsif !is_translation && field&.localized?
|
|
119
|
+
if part < part_count
|
|
120
|
+
current.try(:fetch, method_name, nil)
|
|
121
|
+
else
|
|
122
|
+
fetch_and_demongoize(current, method_name, field)
|
|
123
|
+
end
|
|
124
|
+
elsif is_translation
|
|
125
|
+
current.try(:fetch, method_name, nil)
|
|
126
|
+
else
|
|
127
|
+
fetch_and_demongoize(current, method_name, field)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
# rubocop:enable Metrics/ParameterLists
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -33,6 +33,13 @@ module Mongoid
|
|
|
33
33
|
def deserialize(string)
|
|
34
34
|
BSON::ObjectId.from_string(string)
|
|
35
35
|
end
|
|
36
|
+
|
|
37
|
+
# Returns the klass this serializer handles.
|
|
38
|
+
#
|
|
39
|
+
# @return [ BSON::ObjectId ] The class this serializer handles.
|
|
40
|
+
def klass
|
|
41
|
+
BSON::ObjectId
|
|
42
|
+
end
|
|
36
43
|
end
|
|
37
44
|
end
|
|
38
45
|
end
|
data/lib/mongoid/reloadable.rb
CHANGED
|
@@ -17,6 +17,12 @@ module Mongoid
|
|
|
17
17
|
reloaded = _reload
|
|
18
18
|
check_for_deleted_document!(reloaded)
|
|
19
19
|
|
|
20
|
+
# In an instance where we create a new document, but set the ID to an existing one,
|
|
21
|
+
# when the document is reloaded, we want to set new_record to false.
|
|
22
|
+
# This is necessary otherwise saving will fail, as it will try to insert the document,
|
|
23
|
+
# instead of attempting to update the existing document.
|
|
24
|
+
@new_record = false unless reloaded.nil? || reloaded.empty?
|
|
25
|
+
|
|
20
26
|
reset_object!(reloaded)
|
|
21
27
|
|
|
22
28
|
run_callbacks(:find) unless _find_callbacks.empty?
|
data/lib/mongoid/traversable.rb
CHANGED
|
@@ -131,7 +131,6 @@ module Mongoid
|
|
|
131
131
|
# @param [ String ] value The discriminator key to set.
|
|
132
132
|
#
|
|
133
133
|
# @api private
|
|
134
|
-
# rubocop:disable Metrics/AbcSize
|
|
135
134
|
def discriminator_key=(value)
|
|
136
135
|
raise Errors::InvalidDiscriminatorKeyTarget.new(self, superclass) if hereditary?
|
|
137
136
|
|
|
@@ -159,7 +158,6 @@ module Mongoid
|
|
|
159
158
|
default_proc = -> { self.class.discriminator_value }
|
|
160
159
|
field(discriminator_key, default: default_proc, type: String)
|
|
161
160
|
end
|
|
162
|
-
# rubocop:enable Metrics/AbcSize
|
|
163
161
|
|
|
164
162
|
# Returns the discriminator key.
|
|
165
163
|
#
|
data/lib/mongoid/version.rb
CHANGED
|
@@ -3,6 +3,24 @@
|
|
|
3
3
|
|
|
4
4
|
require 'spec_helper'
|
|
5
5
|
|
|
6
|
+
module EmbedsManySpec
|
|
7
|
+
class Post
|
|
8
|
+
include Mongoid::Document
|
|
9
|
+
field :title, type: String
|
|
10
|
+
embeds_many :comments, class_name: 'EmbedsManySpec::Comment', as: :container
|
|
11
|
+
accepts_nested_attributes_for :comments
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class Comment
|
|
15
|
+
include Mongoid::Document
|
|
16
|
+
field :content, type: String
|
|
17
|
+
validates :content, presence: true
|
|
18
|
+
embedded_in :container, polymorphic: true
|
|
19
|
+
embeds_many :comments, class_name: 'EmbedsManySpec::Comment', as: :container
|
|
20
|
+
accepts_nested_attributes_for :comments
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
6
24
|
describe 'embeds_many associations' do
|
|
7
25
|
|
|
8
26
|
context 're-associating the same object' do
|
|
@@ -201,6 +219,47 @@ describe 'embeds_many associations' do
|
|
|
201
219
|
include_examples 'persists correctly'
|
|
202
220
|
end
|
|
203
221
|
end
|
|
222
|
+
|
|
223
|
+
context 'including duplicates in the assignment' do
|
|
224
|
+
let(:canvas) do
|
|
225
|
+
Canvas.create!(shapes: [Shape.new])
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
shared_examples 'persists correctly' do
|
|
229
|
+
it 'persists correctly' do
|
|
230
|
+
canvas.shapes.length.should eq 2
|
|
231
|
+
_canvas = Canvas.find(canvas.id)
|
|
232
|
+
_canvas.shapes.length.should eq 2
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
context 'via assignment operator' do
|
|
237
|
+
before do
|
|
238
|
+
canvas.shapes = [ canvas.shapes.first, Shape.new, canvas.shapes.first ]
|
|
239
|
+
canvas.save!
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
include_examples 'persists correctly'
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
context 'via attributes=' do
|
|
246
|
+
before do
|
|
247
|
+
canvas.attributes = { shapes: [ canvas.shapes.first, Shape.new, canvas.shapes.first ] }
|
|
248
|
+
canvas.save!
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
include_examples 'persists correctly'
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
context 'via assign_attributes' do
|
|
255
|
+
before do
|
|
256
|
+
canvas.assign_attributes(shapes: [ canvas.shapes.first, Shape.new, canvas.shapes.first ])
|
|
257
|
+
canvas.save!
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
include_examples 'persists correctly'
|
|
261
|
+
end
|
|
262
|
+
end
|
|
204
263
|
end
|
|
205
264
|
|
|
206
265
|
context 'when an anonymous class defines an embeds_many association' do
|
|
@@ -217,4 +276,55 @@ describe 'embeds_many associations' do
|
|
|
217
276
|
expect(klass.new.addresses.build).to be_a Address
|
|
218
277
|
end
|
|
219
278
|
end
|
|
279
|
+
|
|
280
|
+
context 'with deeply nested trees' do
|
|
281
|
+
let(:post) { EmbedsManySpec::Post.create!(title: 'Post') }
|
|
282
|
+
let(:child) { post.comments.create!(content: 'Child') }
|
|
283
|
+
|
|
284
|
+
# creating grandchild will cascade to create the other documents
|
|
285
|
+
let!(:grandchild) { child.comments.create!(content: 'Grandchild') }
|
|
286
|
+
|
|
287
|
+
let(:updated_parent_title) { 'Post Updated' }
|
|
288
|
+
let(:updated_grandchild_content) { 'Grandchild Updated' }
|
|
289
|
+
|
|
290
|
+
context 'with nested attributes' do
|
|
291
|
+
let(:attributes) do
|
|
292
|
+
{
|
|
293
|
+
title: updated_parent_title,
|
|
294
|
+
comments_attributes: [
|
|
295
|
+
{
|
|
296
|
+
# no change for comment1
|
|
297
|
+
_id: child.id,
|
|
298
|
+
comments_attributes: [
|
|
299
|
+
{
|
|
300
|
+
_id: grandchild.id,
|
|
301
|
+
content: updated_grandchild_content,
|
|
302
|
+
}
|
|
303
|
+
]
|
|
304
|
+
}
|
|
305
|
+
]
|
|
306
|
+
}
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
context 'when the grandchild is invalid' do
|
|
310
|
+
let(:updated_grandchild_content) { '' } # invalid value
|
|
311
|
+
|
|
312
|
+
it 'will not save the parent' do
|
|
313
|
+
expect(post.update(attributes)).to be_falsey
|
|
314
|
+
expect(post.errors).not_to be_empty
|
|
315
|
+
expect(post.reload.title).not_to eq(updated_parent_title)
|
|
316
|
+
expect(grandchild.reload.content).not_to eq(updated_grandchild_content)
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
context 'when the grandchild is valid' do
|
|
321
|
+
it 'will save the parent' do
|
|
322
|
+
expect(post.update(attributes)).to be_truthy
|
|
323
|
+
expect(post.errors).to be_empty
|
|
324
|
+
expect(post.reload.title).to eq(updated_parent_title)
|
|
325
|
+
expect(grandchild.reload.content).to eq(updated_grandchild_content)
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
end
|
|
220
330
|
end
|
|
@@ -23,6 +23,38 @@ module HabtmSpec
|
|
|
23
23
|
include Mongoid::Document
|
|
24
24
|
field :file, type: String
|
|
25
25
|
end
|
|
26
|
+
|
|
27
|
+
class Item
|
|
28
|
+
include Mongoid::Document
|
|
29
|
+
|
|
30
|
+
field :title, type: String
|
|
31
|
+
|
|
32
|
+
has_and_belongs_to_many :colors, class_name: 'HabtmSpec::Color', inverse_of: :items
|
|
33
|
+
|
|
34
|
+
accepts_nested_attributes_for :colors
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class Beam
|
|
38
|
+
include Mongoid::Document
|
|
39
|
+
|
|
40
|
+
field :name, type: String
|
|
41
|
+
validates :name, presence: true
|
|
42
|
+
|
|
43
|
+
has_and_belongs_to_many :colors, class_name: 'HabtmSpec::Color', inverse_of: :beams
|
|
44
|
+
|
|
45
|
+
accepts_nested_attributes_for :colors
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class Color
|
|
49
|
+
include Mongoid::Document
|
|
50
|
+
|
|
51
|
+
field :name, type: String
|
|
52
|
+
|
|
53
|
+
has_and_belongs_to_many :items, class_name: 'HabtmSpec::Item', inverse_of: :colors
|
|
54
|
+
has_and_belongs_to_many :beams, class_name: 'HabtmSpec::Beam', inverse_of: :colors
|
|
55
|
+
|
|
56
|
+
accepts_nested_attributes_for :items, :beams
|
|
57
|
+
end
|
|
26
58
|
end
|
|
27
59
|
|
|
28
60
|
describe 'has_and_belongs_to_many associations' do
|
|
@@ -59,4 +91,53 @@ describe 'has_and_belongs_to_many associations' do
|
|
|
59
91
|
expect { image_block.save! }.not_to raise_error
|
|
60
92
|
end
|
|
61
93
|
end
|
|
94
|
+
|
|
95
|
+
context 'with deeply nested trees' do
|
|
96
|
+
let(:item) { HabtmSpec::Item.create!(title: 'Item') }
|
|
97
|
+
let(:beam) { HabtmSpec::Beam.create!(name: 'Beam') }
|
|
98
|
+
let!(:color) { HabtmSpec::Color.create!(name: 'Red', items: [ item ], beams: [ beam ]) }
|
|
99
|
+
|
|
100
|
+
let(:updated_item_title) { 'Item Updated' }
|
|
101
|
+
let(:updated_beam_name) { 'Beam Updated' }
|
|
102
|
+
|
|
103
|
+
context 'with nested attributes' do
|
|
104
|
+
let(:attributes) do
|
|
105
|
+
{
|
|
106
|
+
title: updated_item_title,
|
|
107
|
+
colors_attributes: [
|
|
108
|
+
{
|
|
109
|
+
# no change for color
|
|
110
|
+
_id: color.id,
|
|
111
|
+
beams_attributes: [
|
|
112
|
+
{
|
|
113
|
+
_id: beam.id,
|
|
114
|
+
name: updated_beam_name,
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
context 'when the beam is invalid' do
|
|
123
|
+
let(:updated_beam_name) { '' } # invalid value
|
|
124
|
+
|
|
125
|
+
it 'will not save the parent' do
|
|
126
|
+
expect(item.update(attributes)).to be_falsey
|
|
127
|
+
expect(item.errors).not_to be_empty
|
|
128
|
+
expect(item.reload.title).not_to eq(updated_item_title)
|
|
129
|
+
expect(beam.reload.name).not_to eq(updated_beam_name)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
context 'when the beam is valid' do
|
|
134
|
+
it 'will save the parent' do
|
|
135
|
+
expect(item.update(attributes)).to be_truthy
|
|
136
|
+
expect(item.errors).to be_empty
|
|
137
|
+
expect(item.reload.title).to eq(updated_item_title)
|
|
138
|
+
expect(beam.reload.name).to eq(updated_beam_name)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
62
143
|
end
|
|
@@ -126,4 +126,60 @@ describe 'has_many associations' do
|
|
|
126
126
|
end
|
|
127
127
|
end
|
|
128
128
|
end
|
|
129
|
+
|
|
130
|
+
context 'with deeply nested trees' do
|
|
131
|
+
let(:post) { HmmPost.create!(title: 'Post') }
|
|
132
|
+
let(:child) { post.comments.create!(title: 'Child') }
|
|
133
|
+
|
|
134
|
+
# creating grandchild will cascade to create the other documents
|
|
135
|
+
let!(:grandchild) { child.comments.create!(title: 'Grandchild') }
|
|
136
|
+
|
|
137
|
+
let(:updated_parent_title) { 'Post Updated' }
|
|
138
|
+
let(:updated_grandchild_title) { 'Grandchild Updated' }
|
|
139
|
+
|
|
140
|
+
context 'with nested attributes' do
|
|
141
|
+
let(:attributes) do
|
|
142
|
+
{
|
|
143
|
+
title: updated_parent_title,
|
|
144
|
+
comments_attributes: [
|
|
145
|
+
{
|
|
146
|
+
# no change for comment1
|
|
147
|
+
_id: child.id,
|
|
148
|
+
comments_attributes: [
|
|
149
|
+
{
|
|
150
|
+
_id: grandchild.id,
|
|
151
|
+
title: updated_grandchild_title,
|
|
152
|
+
num: updated_grandchild_num,
|
|
153
|
+
}
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
context 'when the grandchild is invalid' do
|
|
161
|
+
let(:updated_grandchild_num) { -1 } # invalid value
|
|
162
|
+
|
|
163
|
+
it 'will not save the parent' do
|
|
164
|
+
expect(post.update(attributes)).to be_falsey
|
|
165
|
+
expect(post.errors).not_to be_empty
|
|
166
|
+
expect(post.reload.title).not_to eq(updated_parent_title)
|
|
167
|
+
expect(grandchild.reload.title).not_to eq(updated_grandchild_title)
|
|
168
|
+
expect(grandchild.num).not_to eq(updated_grandchild_num)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
context 'when the grandchild is valid' do
|
|
173
|
+
let(:updated_grandchild_num) { 1 }
|
|
174
|
+
|
|
175
|
+
it 'will save the parent' do
|
|
176
|
+
expect(post.update(attributes)).to be_truthy
|
|
177
|
+
expect(post.errors).to be_empty
|
|
178
|
+
expect(post.reload.title).to eq(updated_parent_title)
|
|
179
|
+
expect(grandchild.reload.title).to eq(updated_grandchild_title)
|
|
180
|
+
expect(grandchild.num).to eq(updated_grandchild_num)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
129
185
|
end
|
|
@@ -224,7 +224,7 @@ describe 'has_one associations' do
|
|
|
224
224
|
end
|
|
225
225
|
|
|
226
226
|
context "when explicitly setting the foreign key" do
|
|
227
|
-
let(:comment2) { HomComment.new(
|
|
227
|
+
let(:comment2) { HomComment.new(container_id: post.id, container_type: post.class.name, content: "2") }
|
|
228
228
|
|
|
229
229
|
it "persists the new comment" do
|
|
230
230
|
post.comment = comment1
|
|
@@ -264,10 +264,62 @@ describe 'has_one associations' do
|
|
|
264
264
|
|
|
265
265
|
it "does not overwrite the original value" do
|
|
266
266
|
pending "MONGOID-3999"
|
|
267
|
-
p1 = comment.
|
|
267
|
+
p1 = comment.container
|
|
268
268
|
expect(p1.title).to eq("post 1")
|
|
269
|
-
comment.
|
|
269
|
+
comment.container = post2
|
|
270
270
|
expect(p1.title).to eq("post 1")
|
|
271
271
|
end
|
|
272
272
|
end
|
|
273
|
+
|
|
274
|
+
context 'with deeply nested trees' do
|
|
275
|
+
let(:post) { HomPost.create!(title: 'Post') }
|
|
276
|
+
let(:child) { post.create_comment(content: 'Child') }
|
|
277
|
+
|
|
278
|
+
# creating grandchild will cascade to create the other documents
|
|
279
|
+
let!(:grandchild) { child.create_comment(content: 'Grandchild') }
|
|
280
|
+
|
|
281
|
+
let(:updated_parent_title) { 'Post Updated' }
|
|
282
|
+
let(:updated_grandchild_content) { 'Grandchild Updated' }
|
|
283
|
+
|
|
284
|
+
context 'with nested attributes' do
|
|
285
|
+
let(:attributes) do
|
|
286
|
+
{
|
|
287
|
+
title: updated_parent_title,
|
|
288
|
+
comment_attributes: {
|
|
289
|
+
# no change for child
|
|
290
|
+
_id: child.id,
|
|
291
|
+
comment_attributes: {
|
|
292
|
+
_id: grandchild.id,
|
|
293
|
+
content: updated_grandchild_content,
|
|
294
|
+
num: updated_grandchild_num,
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
context 'when the grandchild is invalid' do
|
|
301
|
+
let(:updated_grandchild_num) { -1 } # invalid value
|
|
302
|
+
|
|
303
|
+
it 'will not save the parent' do
|
|
304
|
+
expect(post.update(attributes)).to be_falsey
|
|
305
|
+
expect(post.errors).not_to be_empty
|
|
306
|
+
expect(post.reload.title).not_to eq(updated_parent_title)
|
|
307
|
+
expect(grandchild.reload.content).not_to eq(updated_grandchild_content)
|
|
308
|
+
expect(grandchild.num).not_to eq(updated_grandchild_num)
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
context 'when the grandchild is valid' do
|
|
313
|
+
let(:updated_grandchild_num) { 1 }
|
|
314
|
+
|
|
315
|
+
it 'will save the parent' do
|
|
316
|
+
expect(post.update(attributes)).to be_truthy
|
|
317
|
+
expect(post.errors).to be_empty
|
|
318
|
+
expect(post.reload.title).to eq(updated_parent_title)
|
|
319
|
+
expect(grandchild.reload.content).to eq(updated_grandchild_content)
|
|
320
|
+
expect(grandchild.num).to eq(updated_grandchild_num)
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
273
325
|
end
|