mongoid 9.0.4 → 9.0.5

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: 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: