Callbacks 底层简要分析

ActiveSupport::Callbacks 本身可分为几部分。

Callback Chain 回调链

define_callbacks 主要作用就是定义一条"回调链",每一条链都是 Callback Chain 的实例对象。

Callback Chain 实例对象,大致如下:

#<ActiveSupport::Callbacks::CallbackChain:0x007fc6ebf70648
  @callbacks=nil, 
  @chain=[], 
  @config={:scope=>[:kind]}, 
  @mutex=#<Mutex:0x007fc6ebf70580>, 
  @name=:checkout>

其中,最重要的信息有:

  • @chain 此回调链所包含的"所有回调"。

  • @name 此回调链的"名字"。

Rails 没有提供查看所有 callback chain 信息的接口,只能间接查看。

比如:

# 所有"回调链"
callback_chains = ObjectSpace.each_object(ActiveSupport::Callbacks::CallbackChain)
# 不为空的"回调链"
chains = callback_chains.select{|cc| cc.instance_variable_get("@chain").present? }

# 所有"回调链"的名字
callback_chains_uniq_name = chains.map(&:name).uniq

但对于某个类而言,则很方便。如:

Callback 回调

上面提到每一条回调链的 @chain 里包含了它"所有的回调",这里的每一个"回调"就是一个 Callback 实例对象。

Callback 实例对象,大致如下:

其中,最重要的信息有:

  • @if@unless 此回调起作用的"前提条件"。

  • @kind 此回调的"回调类型"。(对于 Rails 而言,可以选择 :before, :after, :around 其中之一)

  • @name 此回调所加入的"回调链的名字"。

  • @key 此回调的"名字"。(如果没有名字则用 object_id 代替)

  • @filter 此回调"真正要执行的代码"。(一般只显示名字,也就是说和 @key 一样;如果没有名字,则显示定义它时的文件及行号)

"真正要执行的代码",可以是以下类型:

这些不同类型的"真正要执行的代码",之后都会被 Callback 的 make_lambda 方法转换成 lambda 对象,再然后处理过程类似。

其它

当 @filter 是一个实例对象,并且恰好有和"回调类型"相同的方法

上面也提到"回调类型",Rails 默认有 :before、:after 和 :around. 上面提到了回调里"真正要执行的代码"可以是一个实例对象,当触发回调时,会执行它的同名"回调"方法。

如果 @filter 恰好是一个实例对象,而这个实例对象又恰好有 :before、:after 或 :around,则此时会出现和想像中不一样的结果。

比如:

此时,我们可以使用 define_callbacks 的 scope 参数进行解决。也就是:

Rails 里 Callback 相关的模块及继承关系

Filters 顺序

一条"回调链"上可以有多个"回调",它们彼此之间不是独立的,有先后顺序。即:

  • Before,After,Around

但这些"回调"也有终结的时候。即:

  • End

定义、运行回调

define_callbacks 定义的时候会:

run_callbacks 运行的时候会:

最后更新于

这有帮助吗?