mongoid 8.1.8 → 8.1.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8f349e627130f8eacea25a6242dbcb2d3c48e8c75c7740368afeb62e5939f7c3
4
- data.tar.gz: 1acc6e8d45a07d5d6a140228804eb6f9cdb6b89ac8deeb39283c89338dbbf256
3
+ metadata.gz: d3a2e1b9f4abdd261c6d18c42a1c107089ae415a57c9dcb0978c14891e78330b
4
+ data.tar.gz: 9c09793f70f1dd4ad367e2abcac271245c86f8d6414f262e7643d09538b3bce4
5
5
  SHA512:
6
- metadata.gz: 418c4bf1aac91a436d7d149b5961273c4f704b122d554f76dbe42ff6a29a777e5a3c7dc97c80900c3341cdaabc4e1536e883c00c283fd040cea05b352b2a0a5a
7
- data.tar.gz: 65371b0d3a22ecbeeecae3a9091bc9540921155debff4b556419280bd5c76a3205125e9bb3bb781490031564f7a532dbebe68f72c5ba3c7542690ef3f5308173
6
+ metadata.gz: b0b89ea745ddafadbe1d1cf17c467ee7413eaf94d341bbb209df4db0eeb7cdff6e1fd8b01bc2a85016c47bb830e93ccbcdb0317578c32ee8bbb3dc06eb7db54e
7
+ data.tar.gz: 51627fd03388cbaeef60014b77e2a15202f06f2dffebd69d7ff0cdf5840885a5cdbacb2bc34794eb04c72595c675d5edb55e42b9aa4079dbb5de7d24f454eb8a
@@ -31,6 +31,9 @@ module Mongoid
31
31
  docs_map = {}
32
32
  queue = [ klass.to_s ]
33
33
 
34
+ # account for single-collection inheritance
35
+ queue.push(klass.root_class.to_s) if klass != klass.root_class
36
+
34
37
  while klass = queue.shift
35
38
  if as = assoc_map.delete(klass)
36
39
  as.each do |assoc|
@@ -141,9 +141,13 @@ module Mongoid
141
141
  # @api private
142
142
  def _mongoid_run_child_callbacks(kind, children: nil, &block)
143
143
  if Mongoid::Config.around_callbacks_for_embeds
144
- _mongoid_run_child_callbacks_with_around(kind, children: children, &block)
144
+ _mongoid_run_child_callbacks_with_around(kind,
145
+ children: children,
146
+ &block)
145
147
  else
146
- _mongoid_run_child_callbacks_without_around(kind, children: children, &block)
148
+ _mongoid_run_child_callbacks_without_around(kind,
149
+ children: children,
150
+ &block)
147
151
  end
148
152
  end
149
153
 
@@ -219,9 +223,6 @@ module Mongoid
219
223
  return false if env.halted
220
224
  env.value = !env.halted
221
225
  callback_list << [next_sequence, env]
222
- if (grandchildren = child.send(:cascadable_children, kind))
223
- _mongoid_run_child_before_callbacks(kind, children: grandchildren, callback_list: callback_list)
224
- end
225
226
  end
226
227
  callback_list
227
228
  end
@@ -22,13 +22,20 @@ module Mongoid
22
22
  # @example Set the created at time.
23
23
  # person.set_created_at
24
24
  def set_created_at
25
- if !timeless? && !created_at
25
+ if able_to_set_created_at?
26
26
  time = Time.configured.now
27
27
  self.updated_at = time if is_a?(Updated) && !updated_at_changed?
28
28
  self.created_at = time
29
29
  end
30
30
  clear_timeless_option
31
31
  end
32
+
33
+ # Is the created timestamp able to be set?
34
+ #
35
+ # @return [ true, false ] If the timestamp can be set.
36
+ def able_to_set_created_at?
37
+ !frozen? && !timeless? && !created_at
38
+ end
32
39
  end
33
40
  end
34
41
  end
@@ -323,6 +323,18 @@ module Mongoid
323
323
  !!(Mongoid::Document > superclass)
324
324
  end
325
325
 
326
+ # Returns the root class of the STI tree that the current
327
+ # class participates in. If the class is not an STI subclass, this
328
+ # returns the class itself.
329
+ #
330
+ # @return [ Mongoid::Document ] the root of the STI tree
331
+ def root_class
332
+ root = self
333
+ root = root.superclass while root.hereditary?
334
+
335
+ root
336
+ end
337
+
326
338
  # When inheriting, we want to copy the fields from the parent class and
327
339
  # set the on the child to start, mimicking the behavior of the old
328
340
  # class_inheritable_accessor that was deprecated in Rails edge.
@@ -73,7 +73,7 @@ module Mongoid
73
73
  # use map.all? instead of just all?, because all? will do short-circuit
74
74
  # evaluation and terminate on the first failed validation.
75
75
  list.map do |value|
76
- if value && !value.flagged_for_destroy? && (!value.persisted? || value.changed?)
76
+ if value && !value.flagged_for_destroy?
77
77
  value.validated? ? true : value.valid?
78
78
  else
79
79
  true
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mongoid
4
- VERSION = "8.1.8"
4
+ VERSION = "8.1.9"
5
5
  end
@@ -14,14 +14,36 @@ describe Mongoid::Association::EagerLoadable do
14
14
  Mongoid::Contextual::Mongo.new(criteria)
15
15
  end
16
16
 
17
+ let(:association_host) { Account }
18
+
17
19
  let(:inclusions) do
18
20
  includes.map do |key|
19
- Account.reflect_on_association(key)
21
+ association_host.reflect_on_association(key)
20
22
  end
21
23
  end
22
24
 
23
25
  let(:doc) { criteria.first }
24
26
 
27
+ context 'when root is an STI subclass' do
28
+ # Driver has_one Vehicle
29
+ # Vehicle belongs_to Driver
30
+ # Truck is a Vehicle
31
+
32
+ before do
33
+ Driver.create!(vehicle: Truck.new)
34
+ end
35
+
36
+ let(:criteria) { Truck.all }
37
+ let(:includes) { %i[ driver ] }
38
+ let(:association_host) { Truck }
39
+
40
+ it 'preloads the driver' do
41
+ expect(doc.ivar(:driver)).to be false
42
+ context.preload(inclusions, [ doc ])
43
+ expect(doc.ivar(:driver)).to be == Driver.first
44
+ end
45
+ end
46
+
25
47
  context "when belongs_to" do
26
48
 
27
49
  let!(:account) do
@@ -42,7 +64,7 @@ describe Mongoid::Association::EagerLoadable do
42
64
  it "preloads the parent" do
43
65
  expect(doc.ivar(:person)).to be false
44
66
  context.preload(inclusions, [doc])
45
- expect(doc.ivar(:person)).to eq(doc.person)
67
+ expect(doc.ivar(:person)).to be == person
46
68
  end
47
69
  end
48
70
 
@@ -100,6 +100,66 @@ describe Mongoid::Association do
100
100
  expect(name).to_not be_an_embedded_many
101
101
  end
102
102
  end
103
+
104
+ context "when validation depends on association" do
105
+ before(:all) do
106
+ class Author
107
+ include Mongoid::Document
108
+ embeds_many :books, cascade_callbacks: true
109
+ field :condition, type: Boolean
110
+ end
111
+
112
+ class Book
113
+ include Mongoid::Document
114
+ embedded_in :author
115
+ validate :parent_condition_is_not_true
116
+
117
+ def parent_condition_is_not_true
118
+ return unless author&.condition
119
+ errors.add :base, "Author condition is true."
120
+ end
121
+ end
122
+
123
+ Author.delete_all
124
+ Book.delete_all
125
+ end
126
+
127
+ let(:author) { Author.new }
128
+ let(:book) { Book.new }
129
+
130
+ context "when author is not persisted" do
131
+ it "is valid without books" do
132
+ expect(author.valid?).to be true
133
+ end
134
+
135
+ it "is valid with a book" do
136
+ author.books << book
137
+ expect(author.valid?).to be true
138
+ end
139
+
140
+ it "is not valid when condition is true with a book" do
141
+ author.condition = true
142
+ author.books << book
143
+ expect(author.valid?).to be false
144
+ end
145
+ end
146
+
147
+ context "when author is persisted" do
148
+ before do
149
+ author.books << book
150
+ author.save
151
+ end
152
+
153
+ it "remains valid initially" do
154
+ expect(author.valid?).to be true
155
+ end
156
+
157
+ it "becomes invalid when condition is set to true" do
158
+ author.update_attributes(condition: true)
159
+ expect(author.valid?).to be false
160
+ end
161
+ end
162
+ end
103
163
  end
104
164
 
105
165
  describe "#embedded_one?" do
@@ -388,6 +388,86 @@ describe Mongoid::Interceptable do
388
388
  end
389
389
  end
390
390
  end
391
+
392
+ context 'with embedded grandchildren' do
393
+ IS = InterceptableSpec
394
+
395
+ config_override :prevent_multiple_calls_of_embedded_callbacks, true
396
+
397
+ context 'when creating' do
398
+ let(:registry) { IS::CallbackRegistry.new(only: %i[ before_save ]) }
399
+
400
+ let(:expected_calls) do
401
+ [
402
+ # the parent
403
+ [ IS::CbParent, :before_save ],
404
+
405
+ # the immediate child of the parent
406
+ [ IS::CbCascadedNode, :before_save ],
407
+
408
+ # the grandchild of the parent
409
+ [ IS::CbCascadedNode, :before_save ],
410
+ ]
411
+ end
412
+
413
+ let!(:parent) do
414
+ parent = IS::CbParent.new(registry)
415
+ child = IS::CbCascadedNode.new(registry)
416
+ grandchild = IS::CbCascadedNode.new(registry)
417
+
418
+ child.cb_cascaded_nodes = [ grandchild ]
419
+ parent.cb_cascaded_nodes = [ child ]
420
+
421
+ parent.tap(&:save)
422
+ end
423
+
424
+ it 'should cascade callbacks to grandchildren' do
425
+ expect(registry.calls).to be == expected_calls
426
+ end
427
+ end
428
+
429
+ context 'when updating' do
430
+ let(:registry) { IS::CallbackRegistry.new(only: %i[ before_update ]) }
431
+
432
+ let(:expected_calls) do
433
+ [
434
+ # the parent
435
+ [ IS::CbParent, :before_update ],
436
+
437
+ # the immediate child of the parent
438
+ [ IS::CbCascadedNode, :before_update ],
439
+
440
+ # the grandchild of the parent
441
+ [ IS::CbCascadedNode, :before_update ],
442
+ ]
443
+ end
444
+
445
+ let!(:parent) do
446
+ parent = IS::CbParent.new(nil)
447
+ child = IS::CbCascadedNode.new(nil)
448
+ grandchild = IS::CbCascadedNode.new(nil)
449
+
450
+ child.cb_cascaded_nodes = [ grandchild ]
451
+ parent.cb_cascaded_nodes = [ child ]
452
+
453
+ parent.save
454
+
455
+ parent.callback_registry = registry
456
+ child.callback_registry = registry
457
+ grandchild.callback_registry = registry
458
+
459
+ parent.name = 'updated'
460
+ child.name = 'updated'
461
+ grandchild.name = 'updated'
462
+
463
+ parent.tap(&:save)
464
+ end
465
+
466
+ it 'should cascade callbacks to grandchildren' do
467
+ expect(registry.calls).to be == expected_calls
468
+ end
469
+ end
470
+ end
391
471
  end
392
472
 
393
473
  describe ".before_destroy" do
@@ -1,10 +1,12 @@
1
1
  module InterceptableSpec
2
2
  class CallbackRegistry
3
- def initialize
3
+ def initialize(only: [])
4
4
  @calls = []
5
+ @only = only
5
6
  end
6
7
 
7
8
  def record_call(cls, cb)
9
+ return unless @only.empty? || @only.any? { |pat| pat == cb }
8
10
  @calls << [cls, cb]
9
11
  end
10
12
 
@@ -15,6 +17,8 @@ module InterceptableSpec
15
17
  extend ActiveSupport::Concern
16
18
 
17
19
  included do
20
+ field :name, type: String
21
+
18
22
  %i(
19
23
  validation save create update
20
24
  ).each do |what|
@@ -34,196 +38,128 @@ module InterceptableSpec
34
38
  end
35
39
  end
36
40
  end
37
- end
38
41
 
39
- class CbHasOneParent
40
- include Mongoid::Document
41
-
42
- has_one :child, autosave: true, class_name: "CbHasOneChild", inverse_of: :parent
42
+ attr_accessor :callback_registry
43
43
 
44
- def initialize(callback_registry)
44
+ def initialize(callback_registry, *args, **kwargs)
45
45
  @callback_registry = callback_registry
46
- super()
46
+ super(*args, **kwargs)
47
47
  end
48
+ end
48
49
 
49
- attr_accessor :callback_registry
50
-
50
+ module RootInsertable
51
51
  def insert_as_root
52
52
  @callback_registry&.record_call(self.class, :insert_into_database)
53
53
  super
54
54
  end
55
+ end
55
56
 
57
+ class CbHasOneParent
58
+ include Mongoid::Document
56
59
  include CallbackTracking
60
+ include RootInsertable
61
+
62
+ has_one :child, autosave: true, class_name: "CbHasOneChild", inverse_of: :parent
57
63
  end
58
64
 
59
65
  class CbHasOneChild
60
66
  include Mongoid::Document
67
+ include CallbackTracking
61
68
 
62
69
  belongs_to :parent, class_name: "CbHasOneParent", inverse_of: :child
63
-
64
- def initialize(callback_registry)
65
- @callback_registry = callback_registry
66
- super()
67
- end
68
-
69
- attr_accessor :callback_registry
70
-
71
- include CallbackTracking
72
70
  end
73
71
 
74
72
  class CbHasManyParent
75
73
  include Mongoid::Document
74
+ include CallbackTracking
75
+ include RootInsertable
76
76
 
77
77
  has_many :children, autosave: true, class_name: "CbHasManyChild", inverse_of: :parent
78
-
79
- def initialize(callback_registry)
80
- @callback_registry = callback_registry
81
- super()
82
- end
83
-
84
- attr_accessor :callback_registry
85
-
86
- def insert_as_root
87
- @callback_registry&.record_call(self.class, :insert_into_database)
88
- super
89
- end
90
-
91
- include CallbackTracking
92
78
  end
93
79
 
94
80
  class CbHasManyChild
95
81
  include Mongoid::Document
82
+ include CallbackTracking
96
83
 
97
84
  belongs_to :parent, class_name: "CbHasManyParent", inverse_of: :children
98
-
99
- def initialize(callback_registry)
100
- @callback_registry = callback_registry
101
- super()
102
- end
103
-
104
- attr_accessor :callback_registry
105
-
106
- include CallbackTracking
107
85
  end
108
86
 
109
87
  class CbEmbedsOneParent
110
88
  include Mongoid::Document
89
+ include CallbackTracking
90
+ include RootInsertable
111
91
 
112
92
  field :name
113
93
 
114
94
  embeds_one :child, cascade_callbacks: true, class_name: "CbEmbedsOneChild", inverse_of: :parent
115
-
116
- def initialize(callback_registry)
117
- @callback_registry = callback_registry
118
- super()
119
- end
120
-
121
- attr_accessor :callback_registry
122
-
123
- def insert_as_root
124
- @callback_registry&.record_call(self.class, :insert_into_database)
125
- super
126
- end
127
-
128
- include CallbackTracking
129
95
  end
130
96
 
131
97
  class CbEmbedsOneChild
132
98
  include Mongoid::Document
99
+ include CallbackTracking
133
100
 
134
101
  field :age
135
102
 
136
103
  embedded_in :parent, class_name: "CbEmbedsOneParent", inverse_of: :child
137
-
138
- def initialize(callback_registry)
139
- @callback_registry = callback_registry
140
- super()
141
- end
142
-
143
- attr_accessor :callback_registry
144
-
145
- include CallbackTracking
146
104
  end
147
105
 
148
106
  class CbEmbedsManyParent
149
107
  include Mongoid::Document
108
+ include CallbackTracking
109
+ include RootInsertable
150
110
 
151
111
  embeds_many :children, cascade_callbacks: true, class_name: "CbEmbedsManyChild", inverse_of: :parent
152
-
153
- def initialize(callback_registry)
154
- @callback_registry = callback_registry
155
- super()
156
- end
157
-
158
- attr_accessor :callback_registry
159
-
160
- def insert_as_root
161
- @callback_registry&.record_call(self.class, :insert_into_database)
162
- super
163
- end
164
-
165
- include CallbackTracking
166
112
  end
167
113
 
168
114
  class CbEmbedsManyChild
169
115
  include Mongoid::Document
116
+ include CallbackTracking
170
117
 
171
118
  embedded_in :parent, class_name: "CbEmbedsManyParent", inverse_of: :children
172
-
173
- def initialize(callback_registry)
174
- @callback_registry = callback_registry
175
- super()
176
- end
177
-
178
- attr_accessor :callback_registry
179
-
180
- include CallbackTracking
181
119
  end
182
120
 
183
121
  class CbParent
184
122
  include Mongoid::Document
185
-
186
- def initialize(callback_registry)
187
- @callback_registry = callback_registry
188
- super()
189
- end
190
-
191
- attr_accessor :callback_registry
123
+ include CallbackTracking
192
124
 
193
125
  embeds_many :cb_children
194
126
  embeds_many :cb_cascaded_children, cascade_callbacks: true
195
-
196
- include CallbackTracking
127
+ embeds_many :cb_cascaded_nodes, cascade_callbacks: true, as: :parent
197
128
  end
198
129
 
199
130
  class CbChild
200
131
  include Mongoid::Document
132
+ include CallbackTracking
201
133
 
202
134
  embedded_in :cb_parent
203
-
204
- def initialize(callback_registry, options)
205
- @callback_registry = callback_registry
206
- super(options)
207
- end
208
-
209
- attr_accessor :callback_registry
210
-
211
- include CallbackTracking
212
135
  end
213
136
 
214
137
  class CbCascadedChild
215
138
  include Mongoid::Document
139
+ include CallbackTracking
216
140
 
217
141
  embedded_in :cb_parent
218
142
 
219
- def initialize(callback_registry, options)
220
- @callback_registry = callback_registry
221
- super(options)
222
- end
143
+ before_save :test_mongoid_state
223
144
 
224
- attr_accessor :callback_registry
145
+ private
146
+
147
+ # Helps test that cascading child callbacks have access to the Mongoid
148
+ # state objects; if the implementation uses fiber-local (instead of truly
149
+ # thread-local) variables, the related tests will fail because the
150
+ # cascading child callbacks use fibers to linearize the recursion.
151
+ def test_mongoid_state
152
+ Mongoid::Threaded.stack('interceptable').push(self)
153
+ end
154
+ end
225
155
 
156
+ class CbCascadedNode
157
+ include Mongoid::Document
226
158
  include CallbackTracking
159
+
160
+ embedded_in :parent, polymorphic: true
161
+
162
+ embeds_many :cb_cascaded_nodes, cascade_callbacks: true, as: :parent
227
163
  end
228
164
  end
229
165
 
@@ -43,4 +43,27 @@ describe Mongoid::Timestamps::Created do
43
43
  expect(quiz.created_at).to be_within(10).of(Time.now.utc)
44
44
  end
45
45
  end
46
+
47
+ context "when the document is destroyed" do
48
+ let(:book) do
49
+ Book.create!
50
+ end
51
+
52
+ before do
53
+ Cover.before_save do
54
+ destroy if title == "delete me"
55
+ end
56
+ end
57
+
58
+ after do
59
+ Cover.reset_callbacks(:save)
60
+ end
61
+
62
+ it "does not set the created_at timestamp" do
63
+ book.covers << Cover.new(title: "delete me")
64
+ expect {
65
+ book.save
66
+ }.not_to raise_error
67
+ end
68
+ end
46
69
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mongoid
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.1.8
4
+ version: 8.1.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - The MongoDB Ruby Team
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-01-07 00:00:00.000000000 Z
10
+ date: 2025-01-30 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activemodel
@@ -1159,7 +1159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
1159
1159
  - !ruby/object:Gem::Version
1160
1160
  version: 1.3.6
1161
1161
  requirements: []
1162
- rubygems_version: 3.6.2
1162
+ rubygems_version: 3.6.3
1163
1163
  specification_version: 4
1164
1164
  summary: Elegant Persistence in Ruby for MongoDB.
1165
1165
  test_files: