mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
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:
parent
643862e3be
commit
6d390671f6
4 changed files with 167 additions and 54 deletions
|
@ -30,6 +30,7 @@ module ActiveModel
|
|||
extend ActiveSupport::Autoload
|
||||
|
||||
autoload :AttributeMethods
|
||||
autoload :Callbacks
|
||||
autoload :Conversion
|
||||
autoload :DeprecatedErrorMethods
|
||||
autoload :Dirty
|
||||
|
|
91
activemodel/lib/active_model/callbacks.rb
Normal file
91
activemodel/lib/active_model/callbacks.rb
Normal 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
|
70
activemodel/test/cases/callbacks_test.rb
Normal file
70
activemodel/test/cases/callbacks_test.rb
Normal 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
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue