Extract ActiveSupport::Callbacks from Active Record, test case setup and teardown, and ActionController::Dispatcher. Closes #10727.
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@8664 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
parent
3ffdfa84fc
commit
aae37bb4f7
@ -11,20 +11,6 @@ def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, o
|
||||
new(output).dispatch_cgi(cgi, session_options)
|
||||
end
|
||||
|
||||
# Declare a block to be called before each dispatch.
|
||||
# Run in the order declared.
|
||||
def before_dispatch(*method_names, &block)
|
||||
callbacks[:before].concat method_names
|
||||
callbacks[:before] << block if block_given?
|
||||
end
|
||||
|
||||
# Declare a block to be called after each dispatch.
|
||||
# Run in reverse of the order declared.
|
||||
def after_dispatch(*method_names, &block)
|
||||
callbacks[:after].concat method_names
|
||||
callbacks[:after] << block if block_given?
|
||||
end
|
||||
|
||||
# Add a preparation callback. Preparation callbacks are run before every
|
||||
# request in development mode, and before the first request in production
|
||||
# mode.
|
||||
@ -34,15 +20,16 @@ def after_dispatch(*method_names, &block)
|
||||
# existing callback. Passing an identifier is a suggested practice if the
|
||||
# code adding a preparation block may be reloaded.
|
||||
def to_prepare(identifier = nil, &block)
|
||||
@prepare_dispatch_callbacks ||= []
|
||||
callback = ActiveSupport::Callbacks::Callback.new(:prepare_dispatch, block, :identifier => identifier)
|
||||
|
||||
# Already registered: update the existing callback
|
||||
if identifier
|
||||
if callback = callbacks[:prepare].assoc(identifier)
|
||||
callback[1] = block
|
||||
else
|
||||
callbacks[:prepare] << [identifier, block]
|
||||
end
|
||||
# TODO: Ruby one liner for Array#find returning index
|
||||
if identifier && callback_for_identifier = @prepare_dispatch_callbacks.find { |c| c.identifier == identifier }
|
||||
index = @prepare_dispatch_callbacks.index(callback_for_identifier)
|
||||
@prepare_dispatch_callbacks[index] = callback
|
||||
else
|
||||
callbacks[:prepare] << block
|
||||
@prepare_dispatch_callbacks.concat([callback])
|
||||
end
|
||||
end
|
||||
|
||||
@ -90,12 +77,11 @@ def failsafe_logger
|
||||
cattr_accessor :error_file_path
|
||||
self.error_file_path = "#{::RAILS_ROOT}/public" if defined? ::RAILS_ROOT
|
||||
|
||||
cattr_accessor :callbacks
|
||||
self.callbacks = Hash.new { |h, k| h[k] = [] }
|
||||
|
||||
cattr_accessor :unprepared
|
||||
self.unprepared = true
|
||||
|
||||
include ActiveSupport::Callbacks
|
||||
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
|
||||
|
||||
before_dispatch :reload_application
|
||||
before_dispatch :prepare_application
|
||||
@ -115,12 +101,12 @@ def initialize(output, request = nil, response = nil)
|
||||
def dispatch
|
||||
@@guard.synchronize do
|
||||
begin
|
||||
run_callbacks :before
|
||||
run_callbacks :before_dispatch
|
||||
handle_request
|
||||
rescue Exception => exception
|
||||
failsafe_rescue exception
|
||||
ensure
|
||||
run_callbacks :after, :reverse_each
|
||||
run_callbacks :after_dispatch, :enumerator => :reverse_each
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -152,7 +138,7 @@ def prepare_application(force = false)
|
||||
ActiveRecord::Base.verify_active_connections! if defined?(ActiveRecord)
|
||||
|
||||
if unprepared || force
|
||||
run_callbacks :prepare
|
||||
run_callbacks :prepare_dispatch
|
||||
self.unprepared = false
|
||||
end
|
||||
end
|
||||
@ -177,17 +163,6 @@ def handle_request
|
||||
@controller.process(@request, @response).out(@output)
|
||||
end
|
||||
|
||||
def run_callbacks(kind, enumerator = :each)
|
||||
callbacks[kind].send!(enumerator) do |callback|
|
||||
case callback
|
||||
when Proc; callback.call(self)
|
||||
when String, Symbol; send!(callback)
|
||||
when Array; callback[1].call(self)
|
||||
else raise ArgumentError, "Unrecognized callback #{callback.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def failsafe_rescue(exception)
|
||||
self.class.failsafe_response(@output, '500 Internal Server Error', exception) do
|
||||
if @controller ||= defined?(::ApplicationController) ? ::ApplicationController : Base
|
||||
|
@ -11,7 +11,7 @@ def setup
|
||||
@output = StringIO.new
|
||||
ENV['REQUEST_METHOD'] = 'GET'
|
||||
|
||||
Dispatcher.callbacks[:prepare].clear
|
||||
Dispatcher.instance_variable_set("@prepare_dispatch_callbacks", [])
|
||||
@dispatcher = Dispatcher.new(@output)
|
||||
end
|
||||
|
||||
|
@ -183,14 +183,8 @@ def self.included(base) #:nodoc:
|
||||
base.send :alias_method_chain, method, :callbacks
|
||||
end
|
||||
|
||||
CALLBACKS.each do |method|
|
||||
base.class_eval <<-"end_eval"
|
||||
def self.#{method}(*callbacks, &block)
|
||||
callbacks << block if block_given?
|
||||
write_inheritable_array(#{method.to_sym.inspect}, callbacks)
|
||||
end
|
||||
end_eval
|
||||
end
|
||||
base.send :include, ActiveSupport::Callbacks
|
||||
base.define_callbacks *CALLBACKS
|
||||
end
|
||||
|
||||
# Is called when the object was instantiated by one of the finders, like <tt>Base.find</tt>.
|
||||
@ -301,38 +295,15 @@ def destroy_with_callbacks #:nodoc:
|
||||
def callback(method)
|
||||
notify(method)
|
||||
|
||||
callbacks_for(method).each do |callback|
|
||||
result = case callback
|
||||
when Symbol
|
||||
self.send(callback)
|
||||
when String
|
||||
eval(callback, binding)
|
||||
when Proc, Method
|
||||
callback.call(self)
|
||||
else
|
||||
if callback.respond_to?(method)
|
||||
callback.send(method, self)
|
||||
else
|
||||
raise ActiveRecordError, "Callbacks must be a symbol denoting the method to call, a string to be evaluated, a block to be invoked, or an object responding to the callback method."
|
||||
end
|
||||
end
|
||||
return false if result == false
|
||||
result = run_callbacks(method) { |result, object| result == false }
|
||||
|
||||
if result != false && respond_to_without_attributes?(method)
|
||||
result = send(method)
|
||||
end
|
||||
|
||||
result = send(method) if respond_to_without_attributes?(method)
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
def callbacks_for(method)
|
||||
self.class.read_inheritable_attribute(method.to_sym) or []
|
||||
end
|
||||
|
||||
def invoke_and_notify(method)
|
||||
notify(method)
|
||||
send(method) if respond_to_without_attributes?(method)
|
||||
end
|
||||
|
||||
def notify(method) #:nodoc:
|
||||
self.class.changed
|
||||
self.class.notify_observers(method, self)
|
||||
|
@ -279,6 +279,25 @@ def self.included(base) # :nodoc:
|
||||
alias_method_chain :save!, :validation
|
||||
alias_method_chain :update_attribute, :validation_skipping
|
||||
end
|
||||
|
||||
base.send :include, ActiveSupport::Callbacks
|
||||
|
||||
# TODO: Use helper ActiveSupport::Callbacks#define_callbacks instead
|
||||
%w( validate validate_on_create validate_on_update ).each do |validation_method|
|
||||
base.class_eval <<-"end_eval"
|
||||
def self.#{validation_method}(*methods, &block)
|
||||
options = methods.extract_options!
|
||||
methods << block if block_given?
|
||||
methods.map! { |method| Callback.new(:#{validation_method}, method, options) }
|
||||
existing_methods = read_inheritable_attribute(:#{validation_method}) || []
|
||||
write_inheritable_attribute(:#{validation_method}, existing_methods | methods)
|
||||
end
|
||||
|
||||
def self.#{validation_method}_callback_chain
|
||||
read_inheritable_attribute(:#{validation_method}) || []
|
||||
end
|
||||
end_eval
|
||||
end
|
||||
end
|
||||
|
||||
# All of the following validations are defined in the class scope of the model that you're interested in validating.
|
||||
@ -324,43 +343,6 @@ module ClassMethods
|
||||
# end
|
||||
#
|
||||
# This usage applies to #validate_on_create and #validate_on_update as well.
|
||||
def validate(*methods, &block)
|
||||
methods << block if block_given?
|
||||
write_inheritable_set(:validate, methods)
|
||||
end
|
||||
|
||||
def validate_on_create(*methods, &block)
|
||||
methods << block if block_given?
|
||||
write_inheritable_set(:validate_on_create, methods)
|
||||
end
|
||||
|
||||
def validate_on_update(*methods, &block)
|
||||
methods << block if block_given?
|
||||
write_inheritable_set(:validate_on_update, methods)
|
||||
end
|
||||
|
||||
def condition_block?(condition)
|
||||
condition.respond_to?("call") && (condition.arity == 1 || condition.arity == -1)
|
||||
end
|
||||
|
||||
# Determine from the given condition (whether a block, procedure, method or string)
|
||||
# whether or not to validate the record. See #validates_each.
|
||||
def evaluate_condition(condition, record)
|
||||
case condition
|
||||
when Symbol; record.send(condition)
|
||||
when String; eval(condition, record.instance_eval { binding })
|
||||
else
|
||||
if condition_block?(condition)
|
||||
condition.call(record)
|
||||
else
|
||||
raise(
|
||||
ActiveRecordError,
|
||||
"Validations need to be either a symbol, string (to be eval'ed), proc/method, or " +
|
||||
"class implementing a static validation method"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Validates each attribute against a block.
|
||||
#
|
||||
@ -379,20 +361,17 @@ def evaluate_condition(condition, record)
|
||||
# method, proc or string should return or evaluate to a true or false value.
|
||||
# * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should
|
||||
# not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The
|
||||
# method, proc or string should return or evaluate to a true or false value.
|
||||
# method, proc or string should return or evaluate to a true or false value.
|
||||
def validates_each(*attrs)
|
||||
options = attrs.extract_options!.symbolize_keys
|
||||
attrs = attrs.flatten
|
||||
|
||||
# Declare the validation.
|
||||
send(validation_method(options[:on] || :save)) do |record|
|
||||
# Don't validate when there is an :if condition and that condition is false or there is an :unless condition and that condition is true
|
||||
unless (options[:if] && !evaluate_condition(options[:if], record)) || (options[:unless] && evaluate_condition(options[:unless], record))
|
||||
attrs.each do |attr|
|
||||
value = record.send(attr)
|
||||
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
|
||||
yield record, attr, value
|
||||
end
|
||||
send(validation_method(options[:on] || :save), options) do |record|
|
||||
attrs.each do |attr|
|
||||
value = record.send(attr)
|
||||
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
|
||||
yield record, attr, value
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -515,11 +494,9 @@ def validates_presence_of(*attr_names)
|
||||
|
||||
# can't use validates_each here, because it cannot cope with nonexistent attributes,
|
||||
# while errors.add_on_empty can
|
||||
send(validation_method(configuration[:on])) do |record|
|
||||
unless (configuration[:if] && !evaluate_condition(configuration[:if], record)) || (configuration[:unless] && evaluate_condition(configuration[:unless], record))
|
||||
record.errors.add_on_blank(attr_names, configuration[:message])
|
||||
end
|
||||
end
|
||||
send(validation_method(configuration[:on]), configuration) do |record|
|
||||
record.errors.add_on_blank(attr_names, configuration[:message])
|
||||
end
|
||||
end
|
||||
|
||||
# Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time:
|
||||
@ -911,13 +888,7 @@ def create!(attributes = nil)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def write_inheritable_set(key, methods)
|
||||
existing_methods = read_inheritable_attribute(key) || []
|
||||
write_inheritable_attribute(key, existing_methods | methods)
|
||||
end
|
||||
|
||||
def validation_method(on)
|
||||
case on
|
||||
when :save then :validate
|
||||
@ -959,14 +930,14 @@ def update_attribute_with_validation_skipping(name, value)
|
||||
def valid?
|
||||
errors.clear
|
||||
|
||||
run_validations(:validate)
|
||||
run_callbacks(:validate)
|
||||
validate
|
||||
|
||||
if new_record?
|
||||
run_validations(:validate_on_create)
|
||||
run_callbacks(:validate_on_create)
|
||||
validate_on_create
|
||||
else
|
||||
run_validations(:validate_on_update)
|
||||
run_callbacks(:validate_on_update)
|
||||
validate_on_update
|
||||
end
|
||||
|
||||
@ -990,36 +961,5 @@ def validate_on_create #:doc:
|
||||
# Overwrite this method for validation checks used only on updates.
|
||||
def validate_on_update # :doc:
|
||||
end
|
||||
|
||||
private
|
||||
def run_validations(validation_method)
|
||||
validations = self.class.read_inheritable_attribute(validation_method.to_sym)
|
||||
if validations.nil? then return end
|
||||
validations.each do |validation|
|
||||
if validation.is_a?(Symbol)
|
||||
self.send(validation)
|
||||
elsif validation.is_a?(String)
|
||||
eval(validation, binding)
|
||||
elsif validation_block?(validation)
|
||||
validation.call(self)
|
||||
elsif validation_class?(validation, validation_method)
|
||||
validation.send(validation_method, self)
|
||||
else
|
||||
raise(
|
||||
ActiveRecordError,
|
||||
"Validations need to be either a symbol, string (to be eval'ed), proc/method, or " +
|
||||
"class implementing a static validation method"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def validation_block?(validation)
|
||||
validation.respond_to?("call") && (validation.arity == 1 || validation.arity == -1)
|
||||
end
|
||||
|
||||
def validation_class?(validation, validation_method)
|
||||
validation.respond_to?(validation_method)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1017,7 +1017,7 @@ def test_validate_block
|
||||
|
||||
def test_invalid_validator
|
||||
Topic.validate 3
|
||||
assert_raise(ActiveRecord::ActiveRecordError) { t = Topic.create }
|
||||
assert_raise(ArgumentError) { t = Topic.create }
|
||||
end
|
||||
|
||||
def test_throw_away_typing
|
||||
|
@ -1,5 +1,7 @@
|
||||
*SVN*
|
||||
|
||||
* Extract ActiveSupport::Callbacks from Active Record, test case setup and teardown, and ActionController::Dispatcher. #10727 [Josh Peek]
|
||||
|
||||
* Introducing DateTime #utc, #utc? and #utc_offset, for duck-typing compatibility with Time. Closes #10002 [Geoff Buesing]
|
||||
|
||||
* Time#to_json uses Numeric#to_utc_offset_s to output a cross-platform-consistent representation without having to convert to DateTime. References #9750 [Geoff Buesing]
|
||||
|
@ -26,6 +26,7 @@
|
||||
require 'active_support/vendor'
|
||||
require 'active_support/basic_object'
|
||||
require 'active_support/inflector'
|
||||
require 'active_support/callbacks'
|
||||
|
||||
require 'active_support/core_ext'
|
||||
|
||||
|
94
activesupport/lib/active_support/callbacks.rb
Normal file
94
activesupport/lib/active_support/callbacks.rb
Normal file
@ -0,0 +1,94 @@
|
||||
module ActiveSupport
|
||||
module Callbacks
|
||||
class Callback
|
||||
def self.run(callbacks, object, options = {}, &terminator)
|
||||
enumerator = options[:enumerator] || :each
|
||||
|
||||
unless block_given?
|
||||
callbacks.send(enumerator) { |callback| callback.call(object) }
|
||||
else
|
||||
callbacks.send(enumerator) do |callback|
|
||||
result = callback.call(object)
|
||||
break result if terminator.call(result, object)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :kind, :method, :identifier, :options
|
||||
|
||||
def initialize(kind, method, options = {})
|
||||
@kind = kind
|
||||
@method = method
|
||||
@identifier = options[:identifier]
|
||||
@options = options
|
||||
end
|
||||
|
||||
def call(object)
|
||||
evaluate_method(method, object) if should_run_callback?(object)
|
||||
end
|
||||
|
||||
private
|
||||
def evaluate_method(method, object)
|
||||
case method
|
||||
when Symbol
|
||||
object.send(method)
|
||||
when String
|
||||
eval(method, object.instance_eval { binding })
|
||||
when Proc, Method
|
||||
method.call(object)
|
||||
else
|
||||
if method.respond_to?(kind)
|
||||
method.send(kind, object)
|
||||
else
|
||||
raise ArgumentError,
|
||||
"Callbacks must be a symbol denoting the method to call, a string to be evaluated, " +
|
||||
"a block to be invoked, or an object responding to the callback method."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def should_run_callback?(object)
|
||||
if options[:if]
|
||||
evaluate_method(options[:if], object)
|
||||
elsif options[:unless]
|
||||
!evaluate_method(options[:unless], object)
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.included(base)
|
||||
base.extend ClassMethods
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def define_callbacks(*callbacks)
|
||||
callbacks.each do |callback|
|
||||
class_eval <<-"end_eval"
|
||||
def self.#{callback}(*methods, &block)
|
||||
options = methods.extract_options!
|
||||
methods << block if block_given?
|
||||
callbacks = methods.map { |method| Callback.new(:#{callback}, method, options) }
|
||||
(@#{callback}_callbacks ||= []).concat callbacks
|
||||
end
|
||||
|
||||
def self.#{callback}_callback_chain
|
||||
@#{callback}_callbacks ||= []
|
||||
|
||||
if superclass.respond_to?(:#{callback}_callback_chain)
|
||||
superclass.#{callback}_callback_chain + @#{callback}_callbacks
|
||||
else
|
||||
@#{callback}_callbacks
|
||||
end
|
||||
end
|
||||
end_eval
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def run_callbacks(kind, options = {}, &block)
|
||||
Callback.run(self.class.send("#{kind}_callback_chain"), self, options, &block)
|
||||
end
|
||||
end
|
||||
end
|
@ -2,7 +2,8 @@ module ActiveSupport
|
||||
module Testing
|
||||
module SetupAndTeardown
|
||||
def self.included(base)
|
||||
base.extend ClassMethods
|
||||
base.send :include, ActiveSupport::Callbacks
|
||||
base.define_callbacks :setup, :teardown
|
||||
|
||||
begin
|
||||
require 'mocha'
|
||||
@ -12,38 +13,6 @@ def self.included(base)
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def setup(*method_names, &block)
|
||||
method_names << block if block_given?
|
||||
(@setup_callbacks ||= []).concat method_names
|
||||
end
|
||||
|
||||
def teardown(*method_names, &block)
|
||||
method_names << block if block_given?
|
||||
(@teardown_callbacks ||= []).concat method_names
|
||||
end
|
||||
|
||||
def setup_callback_chain
|
||||
@setup_callbacks ||= []
|
||||
|
||||
if superclass.respond_to?(:setup_callback_chain)
|
||||
superclass.setup_callback_chain + @setup_callbacks
|
||||
else
|
||||
@setup_callbacks
|
||||
end
|
||||
end
|
||||
|
||||
def teardown_callback_chain
|
||||
@teardown_callbacks ||= []
|
||||
|
||||
if superclass.respond_to?(:teardown_callback_chain)
|
||||
superclass.teardown_callback_chain + @teardown_callbacks
|
||||
else
|
||||
@teardown_callbacks
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This redefinition is unfortunate but test/unit shows us no alternative.
|
||||
def run_with_callbacks(result) #:nodoc:
|
||||
return if @method_name.to_s == "default_test"
|
||||
@ -63,7 +32,7 @@ def run_with_callbacks(result) #:nodoc:
|
||||
ensure
|
||||
begin
|
||||
teardown
|
||||
run_callbacks :teardown, :reverse_each
|
||||
run_callbacks :teardown, :enumerator => :reverse_each
|
||||
rescue Test::Unit::AssertionFailedError => e
|
||||
add_failure(e.message, e.backtrace)
|
||||
rescue *Test::Unit::TestCase::PASSTHROUGH_EXCEPTIONS
|
||||
@ -98,7 +67,7 @@ def run_with_callbacks_and_mocha(result)
|
||||
ensure
|
||||
begin
|
||||
teardown
|
||||
run_callbacks :teardown, :reverse_each
|
||||
run_callbacks :teardown, :enumerator => :reverse_each
|
||||
rescue Test::Unit::AssertionFailedError => e
|
||||
add_failure(e.message, e.backtrace)
|
||||
rescue StandardError, ScriptError
|
||||
@ -111,17 +80,6 @@ def run_with_callbacks_and_mocha(result)
|
||||
result.add_run
|
||||
yield(Test::Unit::TestCase::FINISHED, name)
|
||||
end
|
||||
|
||||
protected
|
||||
def run_callbacks(kind, enumerator = :each)
|
||||
self.class.send("#{kind}_callback_chain").send(enumerator) do |callback|
|
||||
case callback
|
||||
when Proc; callback.call(self)
|
||||
when String, Symbol; send!(callback)
|
||||
else raise ArgumentError, "Unrecognized callback #{callback.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
96
activesupport/test/callbacks_test.rb
Normal file
96
activesupport/test/callbacks_test.rb
Normal file
@ -0,0 +1,96 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
class Record
|
||||
include ActiveSupport::Callbacks
|
||||
|
||||
define_callbacks :before_save, :after_save
|
||||
|
||||
class << self
|
||||
def callback_symbol(callback_method)
|
||||
returning("#{callback_method}_method") do |method_name|
|
||||
define_method(method_name) do
|
||||
history << [callback_method, :symbol]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def callback_string(callback_method)
|
||||
"history << [#{callback_method.to_sym.inspect}, :string]"
|
||||
end
|
||||
|
||||
def callback_proc(callback_method)
|
||||
Proc.new { |model| model.history << [callback_method, :proc] }
|
||||
end
|
||||
|
||||
def callback_object(callback_method)
|
||||
klass = Class.new
|
||||
klass.send(:define_method, callback_method) do |model|
|
||||
model.history << [callback_method, :object]
|
||||
end
|
||||
klass.new
|
||||
end
|
||||
end
|
||||
|
||||
def history
|
||||
@history ||= []
|
||||
end
|
||||
end
|
||||
|
||||
class Person < Record
|
||||
[:before_save, :after_save].each do |callback_method|
|
||||
callback_method_sym = callback_method.to_sym
|
||||
send(callback_method, callback_symbol(callback_method_sym))
|
||||
send(callback_method, callback_string(callback_method_sym))
|
||||
send(callback_method, callback_proc(callback_method_sym))
|
||||
send(callback_method, callback_object(callback_method_sym))
|
||||
send(callback_method) { |model| model.history << [callback_method_sym, :block] }
|
||||
end
|
||||
|
||||
def save
|
||||
run_callbacks(:before_save)
|
||||
run_callbacks(:after_save)
|
||||
end
|
||||
end
|
||||
|
||||
class ConditionalPerson < Record
|
||||
before_save Proc.new { |r| r.history << [:before_save, :proc] }, :if => Proc.new { |r| true }
|
||||
before_save Proc.new { |r| r.history << "b00m" }, :if => Proc.new { |r| false }
|
||||
before_save Proc.new { |r| r.history << [:before_save, :proc] }, :unless => Proc.new { |r| false }
|
||||
before_save Proc.new { |r| r.history << "b00m" }, :unless => Proc.new { |r| true }
|
||||
|
||||
def save
|
||||
run_callbacks(:before_save)
|
||||
run_callbacks(:after_save)
|
||||
end
|
||||
end
|
||||
|
||||
class CallbacksTest < Test::Unit::TestCase
|
||||
def test_save_person
|
||||
person = Person.new
|
||||
assert_equal [], person.history
|
||||
person.save
|
||||
assert_equal [
|
||||
[:before_save, :symbol],
|
||||
[:before_save, :string],
|
||||
[:before_save, :proc],
|
||||
[:before_save, :object],
|
||||
[:before_save, :block],
|
||||
[:after_save, :symbol],
|
||||
[:after_save, :string],
|
||||
[:after_save, :proc],
|
||||
[:after_save, :object],
|
||||
[:after_save, :block]
|
||||
], person.history
|
||||
end
|
||||
end
|
||||
|
||||
class ConditionalCallbackTest < Test::Unit::TestCase
|
||||
def test_save_conditional_person
|
||||
person = ConditionalPerson.new
|
||||
person.save
|
||||
assert_equal [
|
||||
[:before_save, :proc],
|
||||
[:before_save, :proc]
|
||||
], person.history
|
||||
end
|
||||
end
|
@ -79,9 +79,9 @@ class SetupAndTeardownTest < Test::Unit::TestCase
|
||||
teardown :foo, :sentinel, :foo
|
||||
|
||||
def test_inherited_setup_callbacks
|
||||
assert_equal [:reset_callback_record, :foo], self.class.setup_callback_chain
|
||||
assert_equal [:reset_callback_record, :foo], self.class.setup_callback_chain.map(&:method)
|
||||
assert_equal [:foo], @called_back
|
||||
assert_equal [:foo, :sentinel, :foo], self.class.teardown_callback_chain
|
||||
assert_equal [:foo, :sentinel, :foo], self.class.teardown_callback_chain.map(&:method)
|
||||
end
|
||||
|
||||
protected
|
||||
@ -104,9 +104,9 @@ class SubclassSetupAndTeardownTest < SetupAndTeardownTest
|
||||
teardown :bar
|
||||
|
||||
def test_inherited_setup_callbacks
|
||||
assert_equal [:reset_callback_record, :foo, :bar], self.class.setup_callback_chain
|
||||
assert_equal [:reset_callback_record, :foo, :bar], self.class.setup_callback_chain.map(&:method)
|
||||
assert_equal [:foo, :bar], @called_back
|
||||
assert_equal [:foo, :sentinel, :foo, :bar], self.class.teardown_callback_chain
|
||||
assert_equal [:foo, :sentinel, :foo, :bar], self.class.teardown_callback_chain.map(&:method)
|
||||
end
|
||||
|
||||
protected
|
||||
|
Loading…
Reference in New Issue
Block a user