active_support/callbacks.rb
用例如下(注意after逆序执行)
class Someone
include ActiveSupport::Callbacks
define_callbacks :walking
set_callback :walking, :before, :clothes
set_callback :walking, :before, :shoes
set_callback :walking, :after, :bath
set_callback :walking, :after, :rest
def walk
run_callbacks :walking do
puts 'walking'
end
end
def clothes
puts 'wear clothes'
end
def shoes
puts 'wear shoes'
end
def bath
puts 'take a bath'
end
def rest
puts 'take a rest'
end
end
Someone.new.walk
# Output
# wear clothes
# wear shoes
# walking
# take a rest
# take a bath
在mixin了ActiveSupport::Callbacks之后,一般就是使用以下三个方法来定义和调用callback
define_callbacks
先看看define_callbacks,它用于定义事件名并给它初始化一个callback chain
module ActiveSupport
module Callbacks
extend Concern
# 当included时,建一个类变量__callbacks
# 其本质是“事件名” => “callback chain”的hash
included do
extend ActiveSupport::DescendantsTracker
class_attribute :__callbacks, instance_writer: false
self.__callbacks ||= {}
end
module ClassMethods
# 用于定义事件名
def define_callbacks(*names)
options = names.extract_options!
names.each do |name|
name = name.to_sym
set_callbacks name, CallbackChain.new(name, options)
# 定义4个写死方法名的方法,分别是
module_eval <<-RUBY, __FILE__, __LINE__ + 1
# 执行事件实体(block),且发起事件名对应的callback
def _run_#{name}_callbacks(&block)
run_callbacks #{name.inspect}, &block
end
# 获取某事件名的callback chain
def self._#{name}_callbacks
get_callbacks(#{name.inspect})
end
# 设置(替换)某事件名的callback chain
def self._#{name}_callbacks=(value)
set_callbacks(#{name.inspect}, value)
end
# 也是获取某事件名的callback chain,不过是实例方法
# 因__callbacks是类变量,所以没问题
def _#{name}_callbacks
__callbacks[#{name.inspect}]
end
RUBY
end
end
protected
def get_callbacks(name) # :nodoc:
__callbacks[name.to_sym]
end
def set_callbacks(name, callbacks) # :nodoc:
self.__callbacks = __callbacks.merge(name.to_sym => callbacks)
end
end
end
end
set_callback
接着,set_callback,用于给事件名绑定callback,必须在define_callbacks之后才能调用,否则找不到事件名对应的callback chain
def set_callback(name, *filter_list, &block)
type, filters, options = normalize_callback_params(filter_list, block)
# if和unless选项会在5.2取消
if options[:if].is_a?(String) || options[:unless].is_a?(String)
ActiveSupport::Deprecation.warn(<<-MSG.squish)
Passing string to :if and :unless conditional options is deprecated
and will be removed in Rails 5.2 without replacement.
MSG
end
# 包装成Callback(这里传的chain其实在build中只会取其name,
# 所以不用担心父类子类chain混用)
self_chain = get_callbacks name
mapped = filters.map do |filter|
Callback.build(self_chain, filter, type, options)
end
# 将刚才的一堆Callback塞到chain中
__update_callbacks(name) do |target, chain|
options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped)
target.set_callbacks name, chain
end
end
# type如果不提供,则默认为:before
# filters就是callback方法名或block,block会排到第一个
# option可有if、unless、prepend(注释里说的)
def normalize_callback_params(filters, block
type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before
options = filters.extract_options!
filters.unshift(block) if block
[type, filters, options.dup]
end
# 给所有子孙类和本类设置同样的callback
# 为什么要reverse_each呢?首先,因为子类的callback理应可比父类丰富,
# 所以父类和子类都要维护自己一套callback chain;其次,而如果先设置父类callback而后子类
# 那么在首次设置时,则子类就会取到父类的
# 已append/prepend callback的callback chain(因为是class_attribute),
# 那么子类再append/prepend就重复了,所以先设置子类的,反正不会覆盖到父类(这是class_attribute的机制)
def __update_callbacks(name)
([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse_each do |target|
chain = target.get_callbacks name
yield target, chain.dup
end
end
run_callbacks
run_callbacks所做的,就是通过传入的事件名kind,找出对应的callback chain,然后compile它。
对于没有around的情况,就是直接invoke_before,再invoke_after,此过程会传递一个env对象,其value是before、事件实体(block)、after的返回值
def run_callbacks(kind)
callbacks = __callbacks[kind.to_sym]
if callbacks.empty?
yield if block_given?
else
env = Filters::Environment.new(self, false, nil)
next_sequence = callbacks.compile
invoke_sequence = Proc.new do
#...
end
# Common case: no 'around' callbacks defined
if next_sequence.final?
next_sequence.invoke_before(env)
env.value = !env.halted && (!block_given? || yield)
next_sequence.invoke_after(env)
env.value
else
invoke_sequence.call
end
end
end
接着看看compile,它会把是chain转换成sequence。chain只是一堆callback的集合,类数组,而sequence的功能更具体,从它的一些方法名invoke_before、invoke_after就可看出
将chain中的callback塞到sequence时,为什么会用reverse呢?应该是没有所谓的,反正apply会作相应的unshift和push,使Before and around callbacks are called in the order that they are set, after callbacks are called in the reverse order
def compile
@callbacks || @mutex.synchronize do
final_sequence = CallbackSequence.new
@callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback|
callback.apply callback_sequence
end
end
end
再接着是apply,这里就开始根据:before/:after/:around来分类了
def apply(callback_sequence)
user_conditions = conditions_lambdas
user_callback = CallTemplate.build(@filter, self)
case kind
when :before
Filters::Before.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config, @filter)
when :after
Filters::After.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config)
when :around
callback_sequence.around(user_callback, user_conditions)
end
end
仅看看Before.build干什么:忽略掉一些过于复杂的条件判断和参数,会发现它会调callback_sequence.before
class Before
def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter)
halted_lambda = chain_config[:terminator]
if user_conditions.any?
halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
else
halting(callback_sequence, user_callback, halted_lambda, filter)
end
end
def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
callback_sequence.before do |env|
# ...
end
end
private_class_method :halting_and_conditional
def self.halting(callback_sequence, user_callback, halted_lambda, filter)
callback_sequence.before do |env|
# ...
end
end
private_class_method :halting
end
类似的After.build会调callback_sequence.after。而CallbackSequence关于before和after的组织是这样的,配合塞callback进来时用的reverse,最后调用的顺序还是:before顺序、after逆序
class CallbackSequence
def initialize(nested = nil, call_template = nil, user_conditions = nil)
# ...
@before = []
@after = []
end
def before(&before)
@before.unshift(before)
self
end
def after(&after)
@after.push(after)
self
end
def invoke_before(arg)
@before.each { |b| b.call(arg) }
end
def invoke_after(arg)
@after.each { |a| a.call(arg) }
end
end
再回到apply,里面有user_callback = CallTemplate.build(@filter, self)和user_callback.make_lambda,而这个才是真正将set_callback的配置转换成可调用的lambda。它是这样的
build会根据set_callback的配置(Symbol、String、还是block),决定怎样操作target(当前类的实例),若果是Symbol,则send;String则instance_exec,block则已是instance_exec并传该实例
class CallTemplate
def initialize(target, method, arguments, block)
@override_target = target
@method_name = method
@arguments = arguments
@override_block = block
end
def expand(target, value, block)
result = @arguments.map { |arg|
case arg
when :value; value
when :target; target
when :block; block || raise(ArgumentError)
end
}
result.unshift @method_name
result.unshift @override_block || block
result.unshift @override_target || target
result
end
def make_lambda
lambda do |target, value, &block|
target, block, method, *arguments = expand(target, value, block)
target.send(method, *arguments, &block)
end
end
def self.build(filter, callback)
case filter
when Symbol
new(nil, filter, [], nil)
when String
new(nil, :instance_exec, [:value], compile_lambda(filter))
when Conditionals::Value
new(filter, :call, [:target, :value], nil)
when ::Proc
if filter.arity > 1
new(nil, :instance_exec, [:target, :block], filter)
elsif filter.arity > 0
new(nil, :instance_exec, [:target], filter)
else
new(nil, :instance_exec, [], filter)
end
else
method_to_call = callback.current_scopes.join("_")
new(filter, method_to_call, [:target], nil)
end
end
end