Move ActiveRecord callbacks implementation to ActiveModel and make use of it.

Signed-off-by: Yehuda Katz <wycats@Yehuda-Katz.local>
This commit is contained in:
José Valim 2009-12-29 01:15:39 +01:00 committed by Yehuda Katz
parent 643862e3be
commit 6d390671f6
4 changed files with 167 additions and 54 deletions

View File

@ -30,6 +30,7 @@ module ActiveModel
extend ActiveSupport::Autoload
autoload :AttributeMethods
autoload :Callbacks
autoload :Conversion
autoload :DeprecatedErrorMethods
autoload :Dirty

View File

@ -0,0 +1,91 @@
require 'active_support/callbacks'
module ActiveModel
module Callbacks
def self.extended(base)
base.class_eval do
include ActiveSupport::Callbacks
end
end
# Define callbacks similar to ActiveRecord ones. It means:
#
# * The callback chain is aborted whenever the block given to
# _run_callbacks returns false.
#
# * If a class is given to the fallback, it will search for
# before_create, around_create and after_create methods.
#
# == Usage
#
# First you need to define which callbacks your model will have:
#
# class MyModel
# define_model_callbacks :create
# end
#
# This will define three class methods: before_create, around_create,
# and after_create. They accept a symbol, a string, an object or a block.
#
# After you create a callback, you need to tell when they are executed.
# For example, you could do:
#
# def create
# _run_create_callbacks do
# super
# end
# end
#
# == Options
#
# define_model_callbacks accepts all options define_callbacks does, in
# case you want to overwrite a default. Besides that, it also accepts
# an :only option, where you can choose if you want all types (before,
# around or after) or just some:
#
# define_model_callbacks :initializer, :only => :after
#
def define_model_callbacks(*callbacks)
options = callbacks.extract_options!
options = { :terminator => "result == false", :scope => [:kind, :name] }.merge(options)
types = Array(options.delete(:only))
types = [:before, :around, :after] if types.empty?
callbacks.each do |callback|
define_callbacks(callback, options)
types.each do |type|
send(:"_define_#{type}_model_callback", self, callback)
end
end
end
def _define_before_model_callback(klass, callback) #:nodoc:
klass.class_eval <<-CALLBACK, __FILE__, __LINE__
def self.before_#{callback}(*args, &block)
set_callback(:#{callback}, :before, *args, &block)
end
CALLBACK
end
def _define_around_model_callback(klass, callback) #:nodoc:
klass.class_eval <<-CALLBACK, __FILE__, __LINE__
def self.around_#{callback}(*args, &block)
set_callback(:#{callback}, :around, *args, &block)
end
CALLBACK
end
def _define_after_model_callback(klass, callback) #:nodoc:
klass.class_eval <<-CALLBACK, __FILE__, __LINE__
def self.after_#{callback}(*args, &block)
options = args.extract_options!
options[:prepend] = true
options[:if] = Array(options[:if]) << "!halted && value != false"
set_callback(:#{callback}, :after, *(args << options), &block)
end
CALLBACK
end
end
end

View File

@ -0,0 +1,70 @@
require "cases/helper"
class CallbacksTest < ActiveModel::TestCase
class CallbackValidator
def around_create(model)
model.callbacks << :before_around_create
yield
model.callbacks << :after_around_create
end
end
class ModelCallbacks
attr_reader :callbacks
extend ActiveModel::Callbacks
define_model_callbacks :create
define_model_callbacks :initialize, :only => :after
before_create :before_create
around_create CallbackValidator.new
after_create do |model|
model.callbacks << :after_create
end
after_create "@callbacks << :final_callback"
def initialize(valid=true)
@callbacks, @valid = [], valid
end
def before_create
@callbacks << :before_create
end
def create
_run_create_callbacks do
@callbacks << :create
@valid
end
end
end
test "complete callback chain" do
model = ModelCallbacks.new
model.create
assert_equal model.callbacks, [ :before_create, :before_around_create, :create,
:after_around_create, :after_create, :final_callback]
end
test "after callbacks are always appended" do
model = ModelCallbacks.new
model.create
assert_equal model.callbacks.last, :final_callback
end
test "after callbacks are not executed if the block returns false" do
model = ModelCallbacks.new(false)
model.create
assert_equal model.callbacks, [ :before_create, :before_around_create,
:create, :after_around_create]
end
test "only selects which types of callbacks should be created" do
assert !ModelCallbacks.respond_to?(:before_initialize)
assert !ModelCallbacks.respond_to?(:around_initialize)
assert ModelCallbacks.respond_to?(:after_initialize)
end
end

View File

@ -1,5 +1,3 @@
require 'observer'
module ActiveRecord
# Callbacks are hooks into the lifecycle of an Active Record object that allow you to trigger logic
# before or after an alteration of the object state. This can be used to make sure that associated and
@ -210,7 +208,6 @@ module ActiveRecord
# instead of quietly returning +false+.
module Callbacks
extend ActiveSupport::Concern
include ActiveSupport::Callbacks
CALLBACKS = [
:after_initialize, :after_find, :before_validation, :after_validation,
@ -224,60 +221,14 @@ module ActiveRecord
alias_method_chain method, :callbacks
end
define_callbacks :initialize, :find, :save, :create, :update, :destroy,
:validation, :terminator => "result == false", :scope => [:kind, :name]
extend ActiveModel::Callbacks
define_model_callbacks :initialize, :find, :only => :after
define_model_callbacks :save, :create, :update, :destroy
define_model_callbacks :validation, :only => [:before, :after]
end
module ClassMethods
def after_initialize(*args, &block)
options = args.extract_options!
options[:prepend] = true
set_callback(:initialize, :after, *(args << options), &block)
end
def after_find(*args, &block)
options = args.extract_options!
options[:prepend] = true
set_callback(:find, :after, *(args << options), &block)
end
[:save, :create, :update, :destroy].each do |callback|
module_eval <<-CALLBACKS, __FILE__, __LINE__
def before_#{callback}(*args, &block)
set_callback(:#{callback}, :before, *args, &block)
end
def around_#{callback}(*args, &block)
set_callback(:#{callback}, :around, *args, &block)
end
def after_#{callback}(*args, &block)
options = args.extract_options!
options[:prepend] = true
options[:if] = Array(options[:if]) << "!halted && value != false"
set_callback(:#{callback}, :after, *(args << options), &block)
end
CALLBACKS
end
def before_validation(*args, &block)
options = args.extract_options!
if options[:on]
options[:if] = Array(options[:if])
options[:if] << "@_on_validate == :#{options[:on]}"
end
set_callback(:validation, :before, *(args << options), &block)
end
def after_validation(*args, &block)
options = args.extract_options!
options[:if] = Array(options[:if])
options[:if] << "!halted"
options[:if] << "@_on_validate == :#{options[:on]}" if options[:on]
options[:prepend] = true
set_callback(:validation, :after, *(args << options), &block)
end
def method_added(meth)
super
if CALLBACKS.include?(meth.to_sym)