Skip to content

Commit c5e092a

Browse files
adviti-mishrajamis
andauthored
5688: Run callbacks for children within fibers (#5837)
* Run callbacks for children within fibers * Comments for run child callbacks with around fibers * Removed the recursive implementation and renamed the fiber implementation to match the original function declaration * Message, summary, and resolution of invalid around callback implementation error * Class for invalid around callback error * try catch block to raise invalid around callbacks error when it is defined without a yield statement (it causes a terminated fiber to be resumed * Require the invalid around callback error * Refactored the rescuing of the exception to make it more elegant * invalid around callback error file is linter-approved * Fixed indentation for the rescue block * Spec for the case a user incorrectly defines an around callback without a yield * raising an invalid around callback error earlier on as soon as a fiber is detected to be dead * Changed the names of the classes for the invalid around callback spec * Modified the class names for the around callback without yield to avoid conflicts * Made the creation fo embedded documents in the around callbacks without yield spec more concise * Update spec/mongoid/interceptable_spec.rb to use logger Co-authored-by: Jamis Buck <jamisbuck@gmail.com> * removed the temporary field --------- Co-authored-by: Jamis Buck <jamisbuck@gmail.com>
1 parent 07761c8 commit c5e092a

File tree

5 files changed

+70
-13
lines changed

5 files changed

+70
-13
lines changed

lib/config/locales/en.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ en:
140140
A collation option is only supported if the query is executed on a MongoDB server
141141
with version >= 3.4."
142142
resolution: "Remove the collation option from the query."
143+
invalid_around_callback:
144+
message: "An around callback must contain a yield in its definition."
145+
summary: "The block needs to be yielded to for around callbacks to function as intended."
146+
resolution: "Ensure there is a yield statement inside the body of the around callback."
143147
invalid_async_query_executor:
144148
message: "Invalid async_query_executor option: %{executor}."
145149
summary: "A invalid async query executor was specified.

lib/mongoid/errors.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
require "mongoid/errors/immutable_attribute"
1414
require "mongoid/errors/in_memory_collation_not_supported"
1515
require "mongoid/errors/invalid_auto_encryption_configuration"
16+
require "mongoid/errors/invalid_around_callback"
1617
require "mongoid/errors/invalid_async_query_executor"
1718
require "mongoid/errors/invalid_collection"
1819
require "mongoid/errors/invalid_config_file"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# frozen_string_literal: true
2+
3+
module Mongoid
4+
module Errors
5+
# This error is raised when an around callback is
6+
# defined by the user without a yield
7+
class InvalidAroundCallback < MongoidError
8+
# Create the new error.
9+
#
10+
# @api private
11+
def initialize
12+
super(compose_message('invalid_around_callback'))
13+
end
14+
end
15+
end
16+
end

lib/mongoid/interceptable.rb

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -161,29 +161,34 @@ def _mongoid_run_child_callbacks(kind, children: nil, &block)
161161
# Execute the callbacks of given kind for embedded documents including
162162
# around callbacks.
163163
#
164-
# @note This method is prone to stack overflow errors if the document
165-
# has a large number of embedded documents. It is recommended to avoid
166-
# using around callbacks for embedded documents until a proper solution
167-
# is implemented.
168-
#
169164
# @param [ Symbol ] kind The type of callback to execute.
170165
# @param [ Array<Document> ] children Children to execute callbacks on. If
171166
# nil, callbacks will be executed on all cascadable children of
172167
# the document.
173168
#
174169
# @api private
175170
def _mongoid_run_child_callbacks_with_around(kind, children: nil, &block)
176-
child, *tail = (children || cascadable_children(kind))
171+
children = (children || cascadable_children(kind))
177172
with_children = !Mongoid::Config.prevent_multiple_calls_of_embedded_callbacks
178-
if child.nil?
179-
block&.call
180-
elsif tail.empty?
181-
child.run_callbacks(child_callback_type(kind, child), with_children: with_children, &block)
182-
else
183-
child.run_callbacks(child_callback_type(kind, child), with_children: with_children) do
184-
_mongoid_run_child_callbacks_with_around(kind, children: tail, &block)
173+
174+
return block&.call if children.empty?
175+
176+
fibers = children.map do |child|
177+
Fiber.new do
178+
child.run_callbacks(child_callback_type(kind, child), with_children: with_children) do
179+
Fiber.yield
180+
end
185181
end
186182
end
183+
184+
fibers.each do |fiber|
185+
fiber.resume
186+
raise Mongoid::Errors::InvalidAroundCallback unless fiber.alive?
187+
end
188+
189+
block&.call
190+
191+
fibers.reverse.each(&:resume)
187192
end
188193

189194
# Execute the callbacks of given kind for embedded documents without

spec/mongoid/interceptable_spec.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2594,4 +2594,35 @@ class TestClass
25942594
end
25952595
end
25962596
end
2597+
2598+
context "when around callbacks for embedded children are enabled" do
2599+
config_override :around_callbacks_for_embeds, true
2600+
2601+
context "when around callback is defined without a yield" do
2602+
class Mother
2603+
include Mongoid::Document
2604+
embeds_many :daughters, cascade_callbacks: true
2605+
end
2606+
2607+
class Daughter
2608+
include Mongoid::Document
2609+
embedded_in :mother
2610+
around_save :log_callback
2611+
2612+
private
2613+
2614+
def log_callback
2615+
logger.debug('callback invoked')
2616+
end
2617+
end
2618+
2619+
let(:mom) { Mother.create(daughters: [ Daughter.new, Daughter.new ]) }
2620+
2621+
it "raises an InvalidAroundCallback error" do
2622+
expect do
2623+
mom.save
2624+
end.to raise_error(Mongoid::Errors::InvalidAroundCallback)
2625+
end
2626+
end
2627+
end
25972628
end

0 commit comments

Comments
 (0)