mongoid 9.0.7 → 9.0.8
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 +61 -0
- data/lib/mongoid/association/nested/many.rb +2 -0
- data/lib/mongoid/association/nested/one.rb +1 -1
- data/lib/mongoid/association/referenced/has_many/proxy.rb +0 -4
- 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/railties/bson_object_id_serializer.rb +7 -0
- data/lib/mongoid/reloadable.rb +6 -0
- 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_models.rb +24 -0
- data/spec/mongoid/association/referenced/has_one_models.rb +10 -2
- 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
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '032258516f15736f4121de82ee4cdcdb625515f653f85a66b0df613665236e4d'
|
4
|
+
data.tar.gz: 443ec24d9a54bb10c87023cd6304081aa48bf92bf5654044cc935f79ca954b9c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3c875bf631d019da208fa59221a74e3468931a802664e43ce2e8ed669e990722a499d8976a8acafb8078dbeff8c02a76df96b9d76f8e33c0c979658f40748fd9
|
7
|
+
data.tar.gz: 46943ea7b081db4784159bacc2056b75b6511f8c8892910dc2c2bfc7ee1229a6ddf0d9141f52137a12a04eea841c24e16b4eae7fb8fc15bd26e2dec7d0273ecc
|
@@ -313,18 +313,19 @@ module Mongoid
|
|
313
313
|
#
|
314
314
|
# @return [ Array<Hash> ] The documents as an array of hashes.
|
315
315
|
def pre_process_batch_insert(docs)
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
316
|
+
[].tap do |results|
|
317
|
+
append_many(docs) do |doc|
|
318
|
+
if persistable? && !_assigning?
|
319
|
+
self.path = doc.atomic_path unless path
|
320
|
+
if doc.valid?(:create)
|
321
|
+
doc.run_before_callbacks(:save, :create)
|
322
|
+
else
|
323
|
+
self.inserts_valid = false
|
324
|
+
end
|
325
325
|
end
|
326
|
+
|
327
|
+
results << doc.send(:as_attributes)
|
326
328
|
end
|
327
|
-
doc.send(:as_attributes)
|
328
329
|
end
|
329
330
|
end
|
330
331
|
|
@@ -443,6 +443,67 @@ module Mongoid
|
|
443
443
|
execute_callback :after_add, document
|
444
444
|
end
|
445
445
|
|
446
|
+
# Returns a unique id for the document, which is either
|
447
|
+
# its _id or its object_id.
|
448
|
+
def id_of(doc)
|
449
|
+
doc._id || doc.object_id
|
450
|
+
end
|
451
|
+
|
452
|
+
# Optimized version of #append that handles multiple documents
|
453
|
+
# in a more efficient way.
|
454
|
+
#
|
455
|
+
# @param [ Array<Document> ] documents The documents to append.
|
456
|
+
#
|
457
|
+
# @return [ EmbedsMany::Proxy ] This proxy instance.
|
458
|
+
def append_many(documents, &block)
|
459
|
+
unique_set = process_incoming_docs(documents, &block)
|
460
|
+
|
461
|
+
_unscoped.concat(unique_set)
|
462
|
+
_target.push(*scope(unique_set))
|
463
|
+
update_attributes_hash
|
464
|
+
|
465
|
+
unique_set.each { |doc| execute_callback :after_add, doc }
|
466
|
+
|
467
|
+
self
|
468
|
+
end
|
469
|
+
|
470
|
+
# Processes the list of documents, building a list of those
|
471
|
+
# that are not already in the association, and preparing
|
472
|
+
# each unique document to be integrated into the association.
|
473
|
+
#
|
474
|
+
# The :before_add callback is executed for each unique document
|
475
|
+
# as part of this step.
|
476
|
+
#
|
477
|
+
# @param [ Array<Document> ] documents The incoming documents to
|
478
|
+
# process.
|
479
|
+
#
|
480
|
+
# @yield [ Document ] Optional block to call for each unique
|
481
|
+
# document.
|
482
|
+
#
|
483
|
+
# @return [ Array<Document> ] The list of unique documents that
|
484
|
+
# do not yet exist in the association.
|
485
|
+
def process_incoming_docs(documents, &block)
|
486
|
+
visited_docs = Set.new(_target.map { |doc| id_of(doc) })
|
487
|
+
next_index = _unscoped.size
|
488
|
+
|
489
|
+
documents.select do |doc|
|
490
|
+
next unless doc
|
491
|
+
|
492
|
+
id = id_of(doc)
|
493
|
+
next if visited_docs.include?(id)
|
494
|
+
|
495
|
+
execute_callback :before_add, doc
|
496
|
+
|
497
|
+
visited_docs.add(id)
|
498
|
+
integrate(doc)
|
499
|
+
|
500
|
+
doc._index = next_index
|
501
|
+
next_index += 1
|
502
|
+
|
503
|
+
block&.call(doc) || true
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
446
507
|
# Instantiate the binding associated with this association.
|
447
508
|
#
|
448
509
|
# @example Create the binding.
|
@@ -1,8 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# TODO: consider refactoring this Proxy class, to satisfy the following
|
4
|
-
# cops...
|
5
|
-
# rubocop:disable Metrics/ClassLength
|
6
3
|
module Mongoid
|
7
4
|
module Association
|
8
5
|
module Referenced
|
@@ -588,4 +585,3 @@ module Mongoid
|
|
588
585
|
end
|
589
586
|
end
|
590
587
|
end
|
591
|
-
# rubocop:enable Metrics/ClassLength
|
data/lib/mongoid/changeable.rb
CHANGED
@@ -15,6 +15,14 @@ module Mongoid
|
|
15
15
|
changed_attributes.keys.select { |attr| attribute_change(attr) }
|
16
16
|
end
|
17
17
|
|
18
|
+
# Indicates that the children of this document may have changed, and
|
19
|
+
# ought to be checked when the document is validated.
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
def children_may_have_changed!
|
23
|
+
@children_may_have_changed = true
|
24
|
+
end
|
25
|
+
|
18
26
|
# Has the document changed?
|
19
27
|
#
|
20
28
|
# @example Has the document changed?
|
@@ -31,7 +39,7 @@ module Mongoid
|
|
31
39
|
#
|
32
40
|
# @return [ true | false ] If any children have changed.
|
33
41
|
def children_changed?
|
34
|
-
_children.any?(&:changed?)
|
42
|
+
@children_may_have_changed || _children.any?(&:changed?)
|
35
43
|
end
|
36
44
|
|
37
45
|
# Get the attribute changes.
|
@@ -69,6 +77,7 @@ module Mongoid
|
|
69
77
|
@previous_changes = changes
|
70
78
|
@attributes_before_last_save = @previous_attributes
|
71
79
|
@previous_attributes = attributes.dup
|
80
|
+
@children_may_have_changed = false
|
72
81
|
reset_atomic_updates!
|
73
82
|
changed_attributes.clear
|
74
83
|
end
|
@@ -92,8 +92,7 @@ module Mongoid
|
|
92
92
|
begin
|
93
93
|
session.with_transaction(options) do
|
94
94
|
yield
|
95
|
-
end
|
96
|
-
run_commit_callbacks(session)
|
95
|
+
end.tap { run_commit_callbacks(session) }
|
97
96
|
rescue *transactions_not_supported_exceptions
|
98
97
|
raise Mongoid::Errors::TransactionsNotSupported
|
99
98
|
rescue Mongoid::Errors::Rollback
|
@@ -213,8 +212,8 @@ module Mongoid
|
|
213
212
|
|
214
213
|
# Transforms custom options for after_commit and after_rollback callbacks
|
215
214
|
# into options for +set_callback+.
|
216
|
-
def set_options_for_callbacks!(args)
|
217
|
-
options = args.extract_options
|
215
|
+
def set_options_for_callbacks!(args, enforced_options = {})
|
216
|
+
options = args.extract_options!.merge(enforced_options)
|
218
217
|
args << options
|
219
218
|
|
220
219
|
if options[:on]
|
data/lib/mongoid/config.rb
CHANGED
@@ -102,7 +102,7 @@ module Mongoid
|
|
102
102
|
#
|
103
103
|
# - :immediate - Initializes a single +Concurrent::ImmediateExecutor+
|
104
104
|
# - :global_thread_pool - Initializes a single +Concurrent::ThreadPoolExecutor+
|
105
|
-
# that uses the +
|
105
|
+
# that uses the +global_executor_concurrency+ for the +max_threads+ value.
|
106
106
|
option :async_query_executor, default: :immediate
|
107
107
|
|
108
108
|
# Defines how many asynchronous queries can be executed concurrently.
|
@@ -27,7 +27,12 @@ module Mongoid
|
|
27
27
|
# If no documents are found, then returned Hash will have
|
28
28
|
# count, sum of 0 and max, min, avg of nil.
|
29
29
|
def aggregates(field)
|
30
|
-
result = collection.aggregate(
|
30
|
+
result = collection.aggregate(
|
31
|
+
pipeline(field),
|
32
|
+
session: _session,
|
33
|
+
hint: view.hint
|
34
|
+
).to_a
|
35
|
+
|
31
36
|
if result.empty?
|
32
37
|
Aggregable::EMPTY_RESULT.dup
|
33
38
|
else
|
@@ -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/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
|
@@ -96,3 +96,27 @@ class HmmAnimal
|
|
96
96
|
|
97
97
|
belongs_to :trainer, class_name: 'HmmTrainer', scope: -> { where(name: 'Dave') }
|
98
98
|
end
|
99
|
+
|
100
|
+
class HmmPost
|
101
|
+
include Mongoid::Document
|
102
|
+
|
103
|
+
field :title, type: String
|
104
|
+
|
105
|
+
has_many :comments, class_name: 'HmmComment', as: :container
|
106
|
+
|
107
|
+
accepts_nested_attributes_for :comments, allow_destroy: true
|
108
|
+
end
|
109
|
+
|
110
|
+
class HmmComment
|
111
|
+
include Mongoid::Document
|
112
|
+
|
113
|
+
field :title, type: String
|
114
|
+
field :num, type: Integer, default: 0
|
115
|
+
|
116
|
+
belongs_to :container, polymorphic: true
|
117
|
+
has_many :comments, class_name: 'HmmComment', as: :container
|
118
|
+
|
119
|
+
accepts_nested_attributes_for :comments, allow_destroy: true
|
120
|
+
|
121
|
+
validates :num, numericality: { greater_than_or_equal_to: 0 }
|
122
|
+
end
|
@@ -102,13 +102,21 @@ class HomPost
|
|
102
102
|
|
103
103
|
field :title, type: String
|
104
104
|
|
105
|
-
has_one :comment,
|
105
|
+
has_one :comment, as: :container, class_name: 'HomComment'
|
106
|
+
|
107
|
+
accepts_nested_attributes_for :comment, allow_destroy: true
|
106
108
|
end
|
107
109
|
|
108
110
|
class HomComment
|
109
111
|
include Mongoid::Document
|
110
112
|
|
111
113
|
field :content, type: String
|
114
|
+
field :num, type: Integer, default: 0
|
115
|
+
|
116
|
+
validates :num, numericality: { greater_than_or_equal_to: 0 }
|
117
|
+
|
118
|
+
belongs_to :container, polymorphic: true, optional: true
|
119
|
+
has_one :comment, as: :container, class_name: 'HomComment'
|
112
120
|
|
113
|
-
|
121
|
+
accepts_nested_attributes_for :comment, allow_destroy: true
|
114
122
|
end
|
@@ -716,7 +716,7 @@ describe Mongoid::Clients::Sessions do
|
|
716
716
|
require_transaction_support
|
717
717
|
|
718
718
|
context 'when no error raised' do
|
719
|
-
|
719
|
+
let!(:person) do
|
720
720
|
Mongoid.transaction do
|
721
721
|
Person.create!
|
722
722
|
end
|
@@ -727,6 +727,10 @@ describe Mongoid::Clients::Sessions do
|
|
727
727
|
expect(other_events.count { |e| e.command_name == 'commitTransaction'}).to be(1)
|
728
728
|
end
|
729
729
|
|
730
|
+
it 'returns the value from the block' do
|
731
|
+
expect(person).to be_a(Person)
|
732
|
+
end
|
733
|
+
|
730
734
|
it 'executes the commands inside the transaction' do
|
731
735
|
expect(Person.count).to be(1)
|
732
736
|
end
|
@@ -787,8 +791,12 @@ describe Mongoid::Clients::Sessions do
|
|
787
791
|
Mongoid::Clients.with_name(:default).database.collections.each(&:drop)
|
788
792
|
TransactionsSpecPerson.collection.create
|
789
793
|
TransactionsSpecPersonWithOnCreate.collection.create
|
794
|
+
TransactionsSpecPersonWithAfterCreateCommit.collection.create
|
790
795
|
TransactionsSpecPersonWithOnUpdate.collection.create
|
796
|
+
TransactionsSpecPersonWithAfterUpdateCommit.collection.create
|
797
|
+
TransactionsSpecPersonWithAfterSaveCommit.collection.create
|
791
798
|
TransactionsSpecPersonWithOnDestroy.collection.create
|
799
|
+
TransactionsSpecPersonWithAfterDestroyCommit.collection.create
|
792
800
|
TransactionSpecRaisesBeforeSave.collection.create
|
793
801
|
TransactionSpecRaisesAfterSave.collection.create
|
794
802
|
end
|
@@ -818,6 +826,18 @@ describe Mongoid::Clients::Sessions do
|
|
818
826
|
|
819
827
|
it_behaves_like 'commit callbacks are called'
|
820
828
|
end
|
829
|
+
|
830
|
+
context 'when callback is after_create_commit' do
|
831
|
+
let!(:subject) do
|
832
|
+
person = nil
|
833
|
+
TransactionsSpecPersonWithAfterCreateCommit.transaction do
|
834
|
+
person = TransactionsSpecPersonWithAfterCreateCommit.create!(name: 'James Bond')
|
835
|
+
end
|
836
|
+
person
|
837
|
+
end
|
838
|
+
|
839
|
+
it_behaves_like 'commit callbacks are called'
|
840
|
+
end
|
821
841
|
end
|
822
842
|
|
823
843
|
context 'save' do
|
@@ -886,6 +906,94 @@ describe Mongoid::Clients::Sessions do
|
|
886
906
|
it_behaves_like 'commit callbacks are called'
|
887
907
|
end
|
888
908
|
end
|
909
|
+
|
910
|
+
context 'with after_update_commit callback' do
|
911
|
+
let(:subject) do
|
912
|
+
TransactionsSpecPersonWithAfterUpdateCommit.create!(name: 'James Bond').tap do |subject|
|
913
|
+
subject.after_commit_counter.reset
|
914
|
+
subject.after_rollback_counter.reset
|
915
|
+
end
|
916
|
+
end
|
917
|
+
|
918
|
+
context 'when modified once' do
|
919
|
+
before do
|
920
|
+
subject.transaction do
|
921
|
+
subject.name = 'Austin Powers'
|
922
|
+
subject.save!
|
923
|
+
end
|
924
|
+
end
|
925
|
+
|
926
|
+
it_behaves_like 'commit callbacks are called'
|
927
|
+
end
|
928
|
+
|
929
|
+
context 'when modified multiple times' do
|
930
|
+
before do
|
931
|
+
subject.transaction do
|
932
|
+
subject.name = 'Austin Powers'
|
933
|
+
subject.save!
|
934
|
+
subject.name = 'Jason Bourne'
|
935
|
+
subject.save!
|
936
|
+
end
|
937
|
+
end
|
938
|
+
|
939
|
+
it_behaves_like 'commit callbacks are called'
|
940
|
+
end
|
941
|
+
end
|
942
|
+
|
943
|
+
context 'with after_save_commit callback' do
|
944
|
+
let(:subject) do
|
945
|
+
TransactionsSpecPersonWithAfterSaveCommit.create!(name: 'James Bond').tap do |subject|
|
946
|
+
subject.after_commit_counter.reset
|
947
|
+
subject.after_rollback_counter.reset
|
948
|
+
end
|
949
|
+
end
|
950
|
+
|
951
|
+
context 'when modified once' do
|
952
|
+
before do
|
953
|
+
subject.transaction do
|
954
|
+
subject.name = 'Austin Powers'
|
955
|
+
subject.save!
|
956
|
+
end
|
957
|
+
end
|
958
|
+
|
959
|
+
it_behaves_like 'commit callbacks are called'
|
960
|
+
end
|
961
|
+
|
962
|
+
context 'when created' do
|
963
|
+
before do
|
964
|
+
TransactionsSpecPersonWithAfterSaveCommit.transaction do
|
965
|
+
subject
|
966
|
+
end
|
967
|
+
end
|
968
|
+
|
969
|
+
it_behaves_like 'commit callbacks are called'
|
970
|
+
end
|
971
|
+
|
972
|
+
context 'when modified multiple times' do
|
973
|
+
before do
|
974
|
+
subject.transaction do
|
975
|
+
subject.name = 'Austin Powers'
|
976
|
+
subject.save!
|
977
|
+
subject.name = 'Jason Bourne'
|
978
|
+
subject.save!
|
979
|
+
end
|
980
|
+
end
|
981
|
+
|
982
|
+
it_behaves_like 'commit callbacks are called'
|
983
|
+
end
|
984
|
+
|
985
|
+
context 'when created and modified' do
|
986
|
+
before do
|
987
|
+
TransactionsSpecPersonWithAfterSaveCommit.transaction do
|
988
|
+
subject
|
989
|
+
subject.name = 'Jason Bourne'
|
990
|
+
subject.save!
|
991
|
+
end
|
992
|
+
end
|
993
|
+
|
994
|
+
it_behaves_like 'commit callbacks are called'
|
995
|
+
end
|
996
|
+
end
|
889
997
|
end
|
890
998
|
|
891
999
|
context 'update_attributes' do
|
@@ -919,6 +1027,34 @@ describe Mongoid::Clients::Sessions do
|
|
919
1027
|
|
920
1028
|
it_behaves_like 'commit callbacks are called'
|
921
1029
|
end
|
1030
|
+
|
1031
|
+
context 'when callback is after_update_commit' do
|
1032
|
+
let(:subject) do
|
1033
|
+
TransactionsSpecPersonWithAfterUpdateCommit.create!(name: 'Jason Bourne')
|
1034
|
+
end
|
1035
|
+
|
1036
|
+
before do
|
1037
|
+
TransactionsSpecPersonWithAfterUpdateCommit.transaction do
|
1038
|
+
subject.update_attributes!(name: 'Foma Kiniaev')
|
1039
|
+
end
|
1040
|
+
end
|
1041
|
+
|
1042
|
+
it_behaves_like 'commit callbacks are called'
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
context 'when callback is after_save_commit' do
|
1046
|
+
let(:subject) do
|
1047
|
+
TransactionsSpecPersonWithAfterSaveCommit.create!(name: 'Jason Bourne')
|
1048
|
+
end
|
1049
|
+
|
1050
|
+
before do
|
1051
|
+
TransactionsSpecPersonWithAfterSaveCommit.transaction do
|
1052
|
+
subject.update_attributes!(name: 'Foma Kiniaev')
|
1053
|
+
end
|
1054
|
+
end
|
1055
|
+
|
1056
|
+
it_behaves_like 'commit callbacks are called'
|
1057
|
+
end
|
922
1058
|
end
|
923
1059
|
|
924
1060
|
context 'destroy' do
|
@@ -971,6 +1107,31 @@ describe Mongoid::Clients::Sessions do
|
|
971
1107
|
|
972
1108
|
it_behaves_like 'commit callbacks are called'
|
973
1109
|
end
|
1110
|
+
|
1111
|
+
context 'with after_destroy_commit' do
|
1112
|
+
let(:after_commit_counter) do
|
1113
|
+
TransactionsSpecCounter.new
|
1114
|
+
end
|
1115
|
+
|
1116
|
+
let(:after_rollback_counter) do
|
1117
|
+
TransactionsSpecCounter.new
|
1118
|
+
end
|
1119
|
+
|
1120
|
+
let(:subject) do
|
1121
|
+
TransactionsSpecPersonWithAfterDestroyCommit.create!(name: 'James Bond').tap do |p|
|
1122
|
+
p.after_commit_counter = after_commit_counter
|
1123
|
+
p.after_rollback_counter = after_rollback_counter
|
1124
|
+
end
|
1125
|
+
end
|
1126
|
+
|
1127
|
+
before do
|
1128
|
+
subject.transaction do
|
1129
|
+
subject.destroy
|
1130
|
+
end
|
1131
|
+
end
|
1132
|
+
|
1133
|
+
it_behaves_like 'commit callbacks are called'
|
1134
|
+
end
|
974
1135
|
end
|
975
1136
|
end
|
976
1137
|
|
@@ -33,6 +33,38 @@ module TransactionsSpecCountable
|
|
33
33
|
def after_rollback_counter=(new_counter)
|
34
34
|
@after_rollback_counter = new_counter
|
35
35
|
end
|
36
|
+
|
37
|
+
def after_save_commit_counter
|
38
|
+
@after_save_commit_counter ||= TransactionsSpecCounter.new
|
39
|
+
end
|
40
|
+
|
41
|
+
def after_save_commit_counter=(new_counter)
|
42
|
+
@after_save_commit_counter = new_counter
|
43
|
+
end
|
44
|
+
|
45
|
+
def after_create_commit_counter
|
46
|
+
@after_create_commit_counter ||= TransactionsSpecCounter.new
|
47
|
+
end
|
48
|
+
|
49
|
+
def after_create_commit_counter=(new_counter)
|
50
|
+
@after_create_commit_counter = new_counter
|
51
|
+
end
|
52
|
+
|
53
|
+
def after_update_commit_counter
|
54
|
+
@after_update_commit_counter ||= TransactionsSpecCounter.new
|
55
|
+
end
|
56
|
+
|
57
|
+
def after_update_commit_counter=(new_counter)
|
58
|
+
@after_update_commit_counter = new_counter
|
59
|
+
end
|
60
|
+
|
61
|
+
def after_destroy_commit_counter
|
62
|
+
@after_destroy_commit_counter ||= TransactionsSpecCounter.new
|
63
|
+
end
|
64
|
+
|
65
|
+
def after_destroy_commit_counter=(new_counter)
|
66
|
+
@after_destroy_commit_counter = new_counter
|
67
|
+
end
|
36
68
|
end
|
37
69
|
|
38
70
|
class TransactionsSpecPerson
|
@@ -65,6 +97,21 @@ class TransactionsSpecPersonWithOnCreate
|
|
65
97
|
end
|
66
98
|
end
|
67
99
|
|
100
|
+
class TransactionsSpecPersonWithAfterCreateCommit
|
101
|
+
include Mongoid::Document
|
102
|
+
include TransactionsSpecCountable
|
103
|
+
|
104
|
+
field :name, type: String
|
105
|
+
|
106
|
+
after_create_commit do
|
107
|
+
after_commit_counter.inc
|
108
|
+
end
|
109
|
+
|
110
|
+
after_rollback on: :create do
|
111
|
+
after_rollback_counter.inc
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
68
115
|
class TransactionsSpecPersonWithOnUpdate
|
69
116
|
include Mongoid::Document
|
70
117
|
include TransactionsSpecCountable
|
@@ -80,6 +127,36 @@ class TransactionsSpecPersonWithOnUpdate
|
|
80
127
|
end
|
81
128
|
end
|
82
129
|
|
130
|
+
class TransactionsSpecPersonWithAfterUpdateCommit
|
131
|
+
include Mongoid::Document
|
132
|
+
include TransactionsSpecCountable
|
133
|
+
|
134
|
+
field :name, type: String
|
135
|
+
|
136
|
+
after_update_commit do
|
137
|
+
after_commit_counter.inc
|
138
|
+
end
|
139
|
+
|
140
|
+
after_rollback on: :create do
|
141
|
+
after_rollback_counter.inc
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
class TransactionsSpecPersonWithAfterSaveCommit
|
146
|
+
include Mongoid::Document
|
147
|
+
include TransactionsSpecCountable
|
148
|
+
|
149
|
+
field :name, type: String
|
150
|
+
|
151
|
+
after_save_commit do
|
152
|
+
after_commit_counter.inc
|
153
|
+
end
|
154
|
+
|
155
|
+
after_rollback on: :create do
|
156
|
+
after_rollback_counter.inc
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
83
160
|
class TransactionsSpecPersonWithOnDestroy
|
84
161
|
include Mongoid::Document
|
85
162
|
include TransactionsSpecCountable
|
@@ -94,6 +171,22 @@ class TransactionsSpecPersonWithOnDestroy
|
|
94
171
|
after_rollback_counter.inc
|
95
172
|
end
|
96
173
|
end
|
174
|
+
|
175
|
+
class TransactionsSpecPersonWithAfterDestroyCommit
|
176
|
+
include Mongoid::Document
|
177
|
+
include TransactionsSpecCountable
|
178
|
+
|
179
|
+
field :name, type: String
|
180
|
+
|
181
|
+
after_destroy_commit do
|
182
|
+
after_commit_counter.inc
|
183
|
+
end
|
184
|
+
|
185
|
+
after_rollback on: :create do
|
186
|
+
after_rollback_counter.inc
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
97
190
|
class TransactionSpecRaisesBeforeSave
|
98
191
|
include Mongoid::Document
|
99
192
|
include TransactionsSpecCountable
|
@@ -244,6 +244,39 @@ describe Mongoid::Contextual::Aggregable::Mongo do
|
|
244
244
|
end
|
245
245
|
end
|
246
246
|
end
|
247
|
+
|
248
|
+
context 'regarding hints' do
|
249
|
+
let(:client) { Person.collection.client }
|
250
|
+
let(:subscriber) { Mrss::EventSubscriber.new }
|
251
|
+
|
252
|
+
before do
|
253
|
+
client.subscribe(Mongo::Monitoring::COMMAND, subscriber)
|
254
|
+
maybe_hint.aggregates(:age)
|
255
|
+
end
|
256
|
+
|
257
|
+
after do
|
258
|
+
client.unsubscribe(Mongo::Monitoring::COMMAND, subscriber)
|
259
|
+
end
|
260
|
+
|
261
|
+
let(:event) { subscriber.single_command_started_event('aggregate') }
|
262
|
+
let(:command) { event.command }
|
263
|
+
|
264
|
+
context 'when no hint is provided' do
|
265
|
+
let(:maybe_hint) { Person }
|
266
|
+
|
267
|
+
it 'does not include the hint in the command' do
|
268
|
+
expect(command['hint']).to be_nil
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
context 'when a hint is provided' do
|
273
|
+
let(:maybe_hint) { Person.hint(age: 1) }
|
274
|
+
|
275
|
+
it 'includes the hint with the command' do
|
276
|
+
expect(command['hint']).to eq({ 'age' => 1 })
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
247
280
|
end
|
248
281
|
|
249
282
|
describe "#avg" do
|
@@ -134,6 +134,16 @@ describe Mongoid::Reloadable do
|
|
134
134
|
|
135
135
|
agent.title.should == '007'
|
136
136
|
end
|
137
|
+
|
138
|
+
it 'sets new_record to false' do
|
139
|
+
expect(agent.new_record?).to be true
|
140
|
+
|
141
|
+
lambda do
|
142
|
+
agent.reload
|
143
|
+
end.should_not raise_error
|
144
|
+
|
145
|
+
expect(agent.new_record?).to be false
|
146
|
+
end
|
137
147
|
end
|
138
148
|
end
|
139
149
|
|
@@ -596,6 +606,20 @@ describe Mongoid::Reloadable do
|
|
596
606
|
band.id.should_not == original_id
|
597
607
|
end
|
598
608
|
end
|
609
|
+
|
610
|
+
context 'when there is no document matching our id' do
|
611
|
+
let(:agent) { Agent.new(id: BSON::ObjectId.new) }
|
612
|
+
|
613
|
+
it 'does not set new_record to false' do
|
614
|
+
expect(agent.new_record?).to be true
|
615
|
+
|
616
|
+
lambda do
|
617
|
+
agent.reload
|
618
|
+
end.should_not raise_error
|
619
|
+
|
620
|
+
expect(agent.new_record?).to be true
|
621
|
+
end
|
622
|
+
end
|
599
623
|
end
|
600
624
|
|
601
625
|
context 'when document has referenced associations' do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongoid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 9.0.
|
4
|
+
version: 9.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- The MongoDB Ruby Team
|
@@ -1229,7 +1229,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
1229
1229
|
- !ruby/object:Gem::Version
|
1230
1230
|
version: 1.3.6
|
1231
1231
|
requirements: []
|
1232
|
-
rubygems_version: 3.7.
|
1232
|
+
rubygems_version: 3.7.2
|
1233
1233
|
specification_version: 4
|
1234
1234
|
summary: Elegant Persistence in Ruby for MongoDB.
|
1235
1235
|
test_files:
|