Add ability to run only before/around/after callbacks in run_callbacks

This commit is contained in:
Trevor Whitaker 2022-09-06 15:21:45 +00:00
parent f9030fe0c6
commit b985dc2b1f
2 changed files with 106 additions and 16 deletions

View File

@ -68,7 +68,7 @@ module ActiveSupport
class_attribute :__callbacks, instance_writer: false, default: {}
end
CALLBACK_FILTER_TYPES = [:before, :after, :around]
CALLBACK_FILTER_TYPES = [:before, :after, :around].freeze
# Runs the callbacks for the given event.
#
@ -92,14 +92,15 @@ module ActiveSupport
# callback can be as noisy as it likes -- but when control has passed
# smoothly through and into the supplied block, we want as little evidence
# as possible that we were here.
def run_callbacks(kind)
def run_callbacks(kind, type = nil)
callbacks = __callbacks[kind.to_sym]
if callbacks.empty?
yield if block_given?
else
env = Filters::Environment.new(self, false, nil)
next_sequence = callbacks.compile
next_sequence = callbacks.compile(type)
# Common case: no 'around' callbacks defined
if next_sequence.final?
@ -612,7 +613,8 @@ module ActiveSupport
terminator: default_terminator
}.merge!(config)
@chain = []
@callbacks = nil
@all_callbacks = nil
@single_callbacks = {}
@mutex = Mutex.new
end
@ -621,32 +623,45 @@ module ActiveSupport
def empty?; @chain.empty?; end
def insert(index, o)
@callbacks = nil
@all_callbacks = nil
@single_callbacks.clear
@chain.insert(index, o)
end
def delete(o)
@callbacks = nil
@all_callbacks = nil
@single_callbacks.clear
@chain.delete(o)
end
def clear
@callbacks = nil
@all_callbacks = nil
@single_callbacks.clear
@chain.clear
self
end
def initialize_copy(other)
@callbacks = nil
@all_callbacks = nil
@single_callbacks = {}
@chain = other.chain.dup
@mutex = Mutex.new
end
def compile
@callbacks || @mutex.synchronize do
final_sequence = CallbackSequence.new
@callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback|
callback.apply callback_sequence
def compile(type)
if type.nil?
@all_callbacks || @mutex.synchronize do
final_sequence = CallbackSequence.new
@all_callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback|
callback.apply(callback_sequence)
end
end
else
@single_callbacks[type] || @mutex.synchronize do
final_sequence = CallbackSequence.new
@single_callbacks[type] ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback|
type == callback.kind ? callback.apply(callback_sequence) : callback_sequence
end
end
end
end
@ -664,19 +679,22 @@ module ActiveSupport
private
def append_one(callback)
@callbacks = nil
@all_callbacks = nil
@single_callbacks.clear
remove_duplicates(callback)
@chain.push(callback)
end
def prepend_one(callback)
@callbacks = nil
@all_callbacks = nil
@single_callbacks.clear
remove_duplicates(callback)
@chain.unshift(callback)
end
def remove_duplicates(callback)
@callbacks = nil
@all_callbacks = nil
@single_callbacks.clear
@chain.delete_if { |c| callback.duplicates?(c) }
end

View File

@ -1187,4 +1187,76 @@ module CallbacksTest
end
end
end
class AllSaveCallbacks
include ActiveSupport::Callbacks
attr_reader :history
define_callbacks :save
def initialize
@history = []
end
set_callback :save, :before, :before_save_1
set_callback :save, :before, :before_save_2
set_callback :save, :around, :around_save_1
set_callback :save, :around, :around_save_2
set_callback :save, :after, :after_save_1
set_callback :save, :after, :after_save_2
def before_save_1
@history << __method__.to_s
end
def before_save_2
@history << __method__.to_s
end
def around_save_1
@history << __method__.to_s + "_before"
yield
@history << __method__.to_s + "_after"
end
def around_save_2
@history << __method__.to_s + "_before"
yield
@history << __method__.to_s + "_after"
end
def after_save_1
@history << __method__.to_s
end
def after_save_2
@history << __method__.to_s
end
end
class RunSpecificCallbackTest < ActiveSupport::TestCase
def test_run_callbacks_only_before
klass = AllSaveCallbacks.new
klass.run_callbacks :save, :before
assert_equal ["before_save_1", "before_save_2"], klass.history
end
def test_run_callbacks_only_around
klass = AllSaveCallbacks.new
klass.run_callbacks :save, :around
assert_equal [
"around_save_1_before",
"around_save_2_before",
"around_save_2_after",
"around_save_1_after"
],
klass.history
end
def test_run_callbacks_only_after
klass = AllSaveCallbacks.new
klass.run_callbacks :save, :after
assert_equal ["after_save_2", "after_save_1"], klass.history
end
end
end