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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 72ad16d0232a1f1c1144801896ec7322b6c452100e02e9bc5c1ba6d21e015e62
4
- data.tar.gz: 5ca24809ea6498805807e80e78d347a8f2d517862d2ace0c39a73fb81918e178
3
+ metadata.gz: '032258516f15736f4121de82ee4cdcdb625515f653f85a66b0df613665236e4d'
4
+ data.tar.gz: 443ec24d9a54bb10c87023cd6304081aa48bf92bf5654044cc935f79ca954b9c
5
5
  SHA512:
6
- metadata.gz: 82a45afa6174672d301145d1910eb6ef4d86433f078fd6514c22d644c101637404f5c2562e497dd3c9576935ce0f267fb3993d3493c520645a85e42799d0601f
7
- data.tar.gz: d01a7d34d5b01aa5b7629b4dad7735f6c86756e86e4cbe6695301f1fe1864043041ccf4beb785bb28e48b2886d85a2c7dc4d54dddbf644a24f07b9852e15ba17
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
- docs.map do |doc|
317
- next unless doc
318
- append(doc)
319
- if persistable? && !_assigning?
320
- self.path = doc.atomic_path unless path
321
- if doc.valid?(:create)
322
- doc.run_before_callbacks(:save, :create)
323
- else
324
- self.inserts_valid = false
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.
@@ -190,6 +190,8 @@ module Mongoid
190
190
  update_document(doc, attrs)
191
191
  existing.push(doc) unless destroyable?(attrs)
192
192
  end
193
+
194
+ parent.children_may_have_changed!
193
195
  end
194
196
  end
195
197
  end
@@ -37,7 +37,7 @@ module Mongoid
37
37
  parent.send(association.setter, nil)
38
38
  else
39
39
  check_for_id_violation!
40
- end
40
+ end.tap { parent.children_may_have_changed! }
41
41
  end
42
42
 
43
43
  # Create the new builder for nested attributes on one-to-one
@@ -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
@@ -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]
@@ -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 +async_query_concurrency+ for the +max_threads+ value.
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(pipeline(field), session: _session).to_a
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
@@ -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?
@@ -5,5 +5,5 @@ module Mongoid
5
5
  #
6
6
  # Note that this file is automatically updated via `rake candidate:create`.
7
7
  # Manual changes to this file will be overwritten by that rake task.
8
- VERSION = '9.0.7'
8
+ VERSION = '9.0.8'
9
9
  end
@@ -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(post_id: post.id, content: "2") }
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.post
267
+ p1 = comment.container
268
268
  expect(p1.title).to eq("post 1")
269
- comment.post = post2
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, inverse_of: :post, class_name: 'HomComment'
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
- belongs_to :post, inverse_of: :comment, optional: true, class_name: 'HomPost'
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
- before do
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.7
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.1
1232
+ rubygems_version: 3.7.2
1233
1233
  specification_version: 4
1234
1234
  summary: Elegant Persistence in Ruby for MongoDB.
1235
1235
  test_files: