Rails Rails 源码分析之 callback 篇

seaify · 2015年08月12日 · 最后由 HDJ 回复于 2019年05月19日 · 4437 次阅读

callback 是什么?

callback,是运行在 object 的生命周期上,某个事件点上的 hook 代码。

callback 在 rails 中,哪些地方有被使用到?

可以发现,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 之上。

ActiveSupport::Callbacks 中的 3 个核心函数

define_callbacks, 定义事件点。set_callback,为事件点安装 callback。run_callbacks,运行某个事件点上安装的 callback。

1. define_callbacks

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。

2. set_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

3. run_callbacks

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 的

给楼主提一个小建议,callback 这个,我个人觉得楼主可以写个完整点的例子,在一个 rails 的 demo 项目里面,一跑起来能出结果,所见即所得,然后大家才会更有兴趣去深入看原理性的东西,这样也更能加深大家对 callback 实现原理的印象。

#1 楼 @jackxu

好建议。

在自己将 callback 的方方面面梳理清楚,以及文字能表达清楚后,会在 github 上追加样例,方便运行查看。

最近就在看 Callback 的代码,我认为你讲的非常不错。对我还是有很大的帮助的

需要 登录 后方可回复, 如果你还没有账号请 注册新账号