mongoid 9.0.4 → 9.0.5

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