ActiveRecord如何利用ActiveSupport::Callbacks
检查before_save定义在哪里
[1] pry(main)> Student.method(:before_save).source_location
=> ["/home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/activemodel-5.1.2/lib/active_model/callbacks.rb", 125]
源码klass.define_singleton_method("before_#{callback}")如下
module ActiveModel
module Callbacks
# 因是增加类方法define_model_callbacks,所以需要extend
# 而为了让define_model_callbacks能使用activesupport的define_callbacks
# 以及其后的before_save等callback定义方法能使用set_callback
# 还要让目标类include ActiveSupport::Callbacks
# 简单来说ActiveModel::Callbacks背后是使用ActiveSupport::Callbacks的
def self.extended(base)
base.class_eval do
include ActiveSupport::Callbacks
end
end
# 为model类定义事件名和对应的callback chain
# (对于activerecord的使用者来说,其实不会直接调用这方法)
def define_model_callbacks(*callbacks)
options = callbacks.extract_options!
options = {
skip_after_callbacks_if_terminated: true,
scope: [:kind, :name],
only: [:before, :around, :after]
}.merge!(options)
types = Array(options.delete(:only))
callbacks.each do |callback|
define_callbacks(callback, options)
# 针对before、around、after,分别定义便利方法
# before_create、before_save等等
types.each do |type|
send("_define_#{type}_model_callback", self, callback)
end
end
end
# 便利方法before_create、before_save等实际上是用set_callback来为事件绑定callback
private
def _define_before_model_callback(klass, callback)
klass.define_singleton_method("before_#{callback}") do |*args, &block|
set_callback(:"#{callback}", :before, *args, &block)
end
end
def _define_around_model_callback(klass, callback)
klass.define_singleton_method("around_#{callback}") do |*args, &block|
set_callback(:"#{callback}", :around, *args, &block)
end
end
# ActiveSupport::Callbacks的after的逆序执行的,但ActiveModel::Callbacks
# 会用prepend: true改变callback入队方式,使它们最终顺序执行
# 同时增加一个value != false的条件,以使invalid的model不执行after的callback
def _define_after_model_callback(klass, callback)
klass.define_singleton_method("after_#{callback}") do |*args, &block|
options = args.extract_options!
options[:prepend] = true
conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v|
v != false
}
options[:if] = Array(options[:if]) << conditional
set_callback(:"#{callback}", :after, *(args << options), &block)
end
end
ActiveRecord本身就自带的before_create、before_save等方法,不难想象ActiveRecord肯定已调用过define_model_callbacks,搜索源码便可发现,它为以下事件(:initialize, :find, :touch, only: :after,:save, :create, :update, :destroy)定义了callback。而validate相关的callabck,则应该在ActiveModel::Validations::Callbacks中
module ActiveRecord
# This module exists because `ActiveRecord::AttributeMethods::Dirty` needs to
# define callbacks, but continue to have its version of `save` be the super
# method of `ActiveRecord::Callbacks`. This will be removed when the removal
# of deprecated code removes this need.
module DefineCallbacks
extend ActiveSupport::Concern
module ClassMethods
include ActiveModel::Callbacks
end
included do
include ActiveModel::Validations::Callbacks
define_model_callbacks :initialize, :find, :touch, only: :after
define_model_callbacks :save, :create, :update, :destroy
end
end
end
这样看来,DefineCallbacks应该是会被include到ActiveRecord中的,搜索源码可发现
module ActiveRecord
class Base
#...
include DefineCallbacks
#...
include Callbacks
#...
end
end
而下面的include Callbacks,则是
module ActiveRecord
module Callbacks
extend ActiveSupport::Concern
CALLBACKS = [
:after_initialize, :after_find, :after_touch, :before_validation, :after_validation,
:before_save, :around_save, :after_save, :before_create, :around_create,
:after_create, :before_update, :around_update, :after_update,
:before_destroy, :around_destroy, :after_destroy, :after_commit, :after_rollback
]
def destroy #:nodoc:
@_destroy_callback_already_called ||= false
return if @_destroy_callback_already_called
@_destroy_callback_already_called = true
_run_destroy_callbacks { super }
rescue RecordNotDestroyed => e
@_association_destroy_exception = e
false
ensure
@_destroy_callback_already_called = false
end
def touch(*) #:nodoc:
_run_touch_callbacks { super }
end
private
def create_or_update(*)
_run_save_callbacks { super }
end
def _create_record
_run_create_callbacks { super }
end
def _update_record(*)
_run_update_callbacks { super }
end
end
end
可见这里直接利用了ActiveSupport::Callbacks的define_callbacks所产生的_run_#{name}_callbacks便利方法来调用callback