@@ -149,6 +149,28 @@ def run_callbacks(kind, with_children: true, skip_if: nil, &block)
149
149
#
150
150
# @api private
151
151
def _mongoid_run_child_callbacks ( kind , children : nil , &block )
152
+ if Mongoid ::Config . around_callbacks_for_embeds
153
+ _mongoid_run_child_callbacks_with_around ( kind , children : children , &block )
154
+ else
155
+ _mongoid_run_child_callbacks_without_around ( kind , children : children , &block )
156
+ end
157
+ end
158
+
159
+ # Execute the callbacks of given kind for embedded documents including
160
+ # around callbacks.
161
+ #
162
+ # @note This method is prone to stack overflow errors if the document
163
+ # has a large number of embedded documents. It is recommended to avoid
164
+ # using around callbacks for embedded documents until a proper solution
165
+ # is implemented.
166
+ #
167
+ # @param [ Symbol ] kind The type of callback to execute.
168
+ # @param [ Array<Document> ] children Children to execute callbacks on. If
169
+ # nil, callbacks will be executed on all cascadable children of
170
+ # the document.
171
+ #
172
+ # @api private
173
+ def _mongoid_run_child_callbacks_with_around ( kind , children : nil , &block )
152
174
child , *tail = ( children || cascadable_children ( kind ) )
153
175
with_children = !Mongoid ::Config . prevent_multiple_calls_of_embedded_callbacks
154
176
if child . nil?
@@ -157,11 +179,73 @@ def _mongoid_run_child_callbacks(kind, children: nil, &block)
157
179
child . run_callbacks ( child_callback_type ( kind , child ) , with_children : with_children , &block )
158
180
else
159
181
child . run_callbacks ( child_callback_type ( kind , child ) , with_children : with_children ) do
160
- _mongoid_run_child_callbacks ( kind , children : tail , &block )
182
+ _mongoid_run_child_callbacks_with_around ( kind , children : tail , &block )
161
183
end
162
184
end
163
185
end
164
186
187
+ # Execute the callbacks of given kind for embedded documents without
188
+ # around callbacks.
189
+ #
190
+ # @param [ Symbol ] kind The type of callback to execute.
191
+ # @param [ Array<Document> ] children Children to execute callbacks on. If
192
+ # nil, callbacks will be executed on all cascadable children of
193
+ # the document.
194
+ #
195
+ # @api private
196
+ def _mongoid_run_child_callbacks_without_around ( kind , children : nil , &block )
197
+ children = ( children || cascadable_children ( kind ) )
198
+ callback_list = _mongoid_run_child_before_callbacks ( kind , children : children )
199
+ return false if callback_list == false
200
+ value = block &.call
201
+ callback_list . each do |_next_sequence , env |
202
+ env . value &&= value
203
+ end
204
+ return false if _mongoid_run_child_after_callbacks ( callback_list : callback_list ) == false
205
+
206
+ value
207
+ end
208
+
209
+ # Execute the before callbacks of given kind for embedded documents.
210
+ #
211
+ # @param [ Symbol ] kind The type of callback to execute.
212
+ # @param [ Array<Document> ] children Children to execute callbacks on.
213
+ # @param [ Array<ActiveSupport::Callbacks::CallbackSequence, ActiveSupport::Callbacks::Filters::Environment> ] callback_list List of
214
+ # pairs of callback sequence and environment. This list will be later used
215
+ # to execute after callbacks in reverse order.
216
+ #
217
+ # @api private
218
+ def _mongoid_run_child_before_callbacks ( kind , children : [ ] , callback_list : [ ] )
219
+ children . each do |child |
220
+ chain = child . __callbacks [ child_callback_type ( kind , child ) ]
221
+ env = ActiveSupport ::Callbacks ::Filters ::Environment . new ( child , false , nil )
222
+ next_sequence = compile_callbacks ( chain )
223
+ unless next_sequence . final?
224
+ Mongoid . logger . warn ( "Around callbacks are disabled for embedded documents. Skipping around callbacks for #{ child . class . name } ." )
225
+ Mongoid . logger . warn ( "To enable around callbacks for embedded documents, set Mongoid::Config.around_callbacks_for_embeds to true." )
226
+ end
227
+ next_sequence . invoke_before ( env )
228
+ return false if env . halted
229
+ env . value = !env . halted
230
+ callback_list << [ next_sequence , env ]
231
+ if ( grandchildren = child . send ( :cascadable_children , kind ) )
232
+ _mongoid_run_child_before_callbacks ( kind , children : grandchildren , callback_list : callback_list )
233
+ end
234
+ end
235
+ callback_list
236
+ end
237
+
238
+ # Execute the after callbacks.
239
+ #
240
+ # @param [ Array<ActiveSupport::Callbacks::CallbackSequence, ActiveSupport::Callbacks::Filters::Environment> ] callback_list List of
241
+ # pairs of callback sequence and environment.
242
+ def _mongoid_run_child_after_callbacks ( callback_list : [ ] )
243
+ callback_list . reverse_each do |next_sequence , env |
244
+ next_sequence . invoke_after ( env )
245
+ return false if env . halted
246
+ end
247
+ end
248
+
165
249
# Returns the stored callbacks to be executed later.
166
250
#
167
251
# @return [ Array<Symbol> ] Method symbols of the stored pending callbacks.
@@ -314,13 +398,7 @@ def run_targeted_callbacks(place, kind)
314
398
end
315
399
self . class . send :define_method , name do
316
400
env = ActiveSupport ::Callbacks ::Filters ::Environment . new ( self , false , nil )
317
- sequence = if chain . method ( :compile ) . arity == 0
318
- # ActiveSupport < 7.1
319
- chain . compile
320
- else
321
- # ActiveSupport >= 7.1
322
- chain . compile ( nil )
323
- end
401
+ sequence = compile_callbacks ( chain )
324
402
sequence . invoke_before ( env )
325
403
env . value = !env . halted
326
404
sequence . invoke_after ( env )
@@ -330,5 +408,24 @@ def run_targeted_callbacks(place, kind)
330
408
end
331
409
send ( name )
332
410
end
411
+
412
+ # Compile the callback chain.
413
+ #
414
+ # This method hides the differences between ActiveSupport implementations
415
+ # before and after 7.1.
416
+ #
417
+ # @param [ ActiveSupport::Callbacks::CallbackChain ] chain The callback chain.
418
+ # @param [ Symbol | nil ] type The type of callback chain to compile.
419
+ #
420
+ # @return [ ActiveSupport::Callbacks::CallbackSequence ] The compiled callback sequence.
421
+ def compile_callbacks ( chain , type = nil )
422
+ if chain . method ( :compile ) . arity == 0
423
+ # ActiveSupport < 7.1
424
+ chain . compile
425
+ else
426
+ # ActiveSupport >= 7.1
427
+ chain . compile ( type )
428
+ end
429
+ end
333
430
end
334
431
end
0 commit comments