用例如下(注意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