callback,是运行在 object 的生命周期上,某个事件点上的 hook 代码。
可以发现,abstract_controller, action_dispatch, active_job, active_model, active_record, active_support 都使用到了 callback
chuck@chuck-MacBook-Pro:~/seaify/rails(master|…) % find . -name callbacks.rb
./actionpack/lib/abstract_controller/callbacks.rb
./actionpack/lib/action_dispatch/middleware/callbacks.rb
./activejob/lib/active_job/callbacks.rb
./activemodel/lib/active_model/callbacks.rb
./activemodel/lib/active_model/validations/callbacks.rb
./activerecord/lib/active_record/callbacks.rb
./activesupport/lib/active_support/callbacks.rb
更具体的是,ActionController::Base 中的 before_action, ActiveRecord::Base 中的 before_save, ActiveJob::Base 中的 before_enqueue 等 正是 callback 的使用例子,ActiveSupport::Callbacks 提供了 callback 相关的最基本的功能,而 ActionController::Base, ActiveRecord::Base 等都是在建立在 ActiveSupport::Callbacks 之上。
define_callbacks, 定义事件点。set_callback,为事件点安装 callback。run_callbacks,运行某个事件点上安装的 callback。
define_callbacks,在 object 的生命周期中定义事件点,而且这些事件点,并不是必须是 update, save 这些,可以自己定义,比如电商系统中,我们可以增加事件点 update_order,去在 update_order 这个事件的 before, around, after 这些点上注册 callback,如用来发送短信,您的订单以及发货啦,售后审核通过啦。
class Record
include ActiveSupport::Callbacks
define_callbacks :update_order
def update_order
run_callbacks :update_order do
puts "- update_order"
end
end
end
而对于 ActiveModel::Base 来说,create, update, destroy 等等这些事件点已经事先定义好,而且该事件的对应函数也写好了。
define_callbacks 源码
def define_callbacks(*names)
options = names.extract_options!
names.each do |name|
class_attribute "_#{name}_callbacks"
set_callbacks name, CallbackChain.new(name, options)
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def _run_#{name}_callbacks(&block)
__run_callbacks__(_#{name}_callbacks, &block)
end
RUBY
end
end
可以看到如果有 define_callbacks :initialize, :save, :destroy,则其实际上,会新生成_run_initialize_callbacks, _run_save_callbacks, _run_destroy_callbacks 这 3 个函数,用来在 run_callbacks 中被调用。同时为 initialize, save, destroy 生成了 3 个 callback chain,用来存储单个 event 的所有 callback。比如在某个 controller 中,before_action 是可以多次被定义的,也就是对于 before_action,有多个 callback。
可以看到,根据传递进来的参数,首先构造出了一个名为 mapped 的 callback 数组,然后根据 prepend 属性,来判断这个 callback 数组,是插入在原有的 CallbackChain 的尾部,还是头部。
def set_callback(name, *filter_list, &block)
type, filters, options = normalize_callback_params(filter_list, block)
self_chain = get_callbacks name
mapped = filters.map do |filter|
Callback.build(self_chain, filter, type, options)
end
__update_callbacks(name) do |target, chain|
options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped)
target.set_callbacks name, chain
end
end
run_callbacks,最终会调用run_callbacks,通过 callbacks.compile, 会去生成 CallbackSequence,并且调用它的 call 函数
def __run_callbacks__(callbacks, &block)
if callbacks.empty?
yield if block_given?
else
runner = callbacks.compile
e = Filters::Environment.new(self, false, nil, block)
runner.call(e).value
end
end
而 CallbackSequence#call
def call(arg)
@before.each { |b| b.call(arg) }
value = @call.call(arg)
@after.each { |a| a.call(arg) }
value
end
可以看到首先是去执行@before
这个数组中的各个 callback, 接着执行传递进来的代码块,然后去执行@after
这个数组中的各个 callback。
callback 的 filter,以及顺序,以及 activemodel 等是怎样使用 ActiveSupport::Autoload 的