mirror of
https://github.com/github/rails.git
synced 2026-04-26 03:00:59 -04:00
Merge commit 'josevalim/callbacks'
This commit is contained in:
@@ -485,7 +485,14 @@ module ActiveRecord
|
||||
|
||||
def callback(method, record)
|
||||
callbacks_for(method).each do |callback|
|
||||
ActiveSupport::DeprecatedCallbacks::Callback.new(method, callback, record).call(@owner, record)
|
||||
case callback
|
||||
when Symbol
|
||||
@owner.send(callback, record)
|
||||
when Proc
|
||||
callback.call(@owner, record)
|
||||
else
|
||||
callback.send(method, @owner, record)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -274,7 +274,7 @@ module ActiveRecord
|
||||
|
||||
def deprecated_callback_method(symbol) #:nodoc:
|
||||
if respond_to?(symbol)
|
||||
ActiveSupport::Deprecation.warn("Base##{symbol} has been deprecated, please use Base.#{symbol} :method instead")
|
||||
ActiveSupport::Deprecation.warn("Overwriting #{symbol} in your models has been deprecated, please use Base##{symbol} :method_name instead")
|
||||
send(symbol)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,8 +17,6 @@ module ActiveRecord
|
||||
|
||||
module Validations
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include ActiveSupport::DeprecatedCallbacks
|
||||
include ActiveModel::Validations
|
||||
|
||||
included do
|
||||
|
||||
@@ -50,7 +50,6 @@ module ActiveSupport
|
||||
autoload :Callbacks
|
||||
autoload :Concern
|
||||
autoload :Configurable
|
||||
autoload :DeprecatedCallbacks
|
||||
autoload :Deprecation
|
||||
autoload :Gzip
|
||||
autoload :Inflector
|
||||
|
||||
@@ -90,7 +90,7 @@ module ActiveSupport
|
||||
class Callback
|
||||
@@_callback_sequence = 0
|
||||
|
||||
attr_accessor :chain, :filter, :kind, :options, :per_key, :klass
|
||||
attr_accessor :chain, :filter, :kind, :options, :per_key, :klass, :raw_filter
|
||||
|
||||
def initialize(chain, filter, kind, options, klass)
|
||||
@chain, @kind, @klass = chain, kind, klass
|
||||
|
||||
@@ -1,284 +0,0 @@
|
||||
require 'active_support/core_ext/array/extract_options'
|
||||
require 'active_support/core_ext/array/wrap'
|
||||
|
||||
module ActiveSupport
|
||||
# Callbacks are hooks into the lifecycle of an object that allow you to trigger logic
|
||||
# before or after an alteration of the object state.
|
||||
#
|
||||
# Mixing in this module allows you to define callbacks in your class.
|
||||
#
|
||||
# Example:
|
||||
# class Storage
|
||||
# include ActiveSupport::DeprecatedCallbacks
|
||||
#
|
||||
# define_callbacks :before_save, :after_save
|
||||
# end
|
||||
#
|
||||
# class ConfigStorage < Storage
|
||||
# before_save :saving_message
|
||||
# def saving_message
|
||||
# puts "saving..."
|
||||
# end
|
||||
#
|
||||
# after_save do |object|
|
||||
# puts "saved"
|
||||
# end
|
||||
#
|
||||
# def save
|
||||
# run_callbacks(:before_save)
|
||||
# puts "- save"
|
||||
# run_callbacks(:after_save)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# config = ConfigStorage.new
|
||||
# config.save
|
||||
#
|
||||
# Output:
|
||||
# saving...
|
||||
# - save
|
||||
# saved
|
||||
#
|
||||
# Callbacks from parent classes are inherited.
|
||||
#
|
||||
# Example:
|
||||
# class Storage
|
||||
# include ActiveSupport::DeprecatedCallbacks
|
||||
#
|
||||
# define_callbacks :before_save, :after_save
|
||||
#
|
||||
# before_save :prepare
|
||||
# def prepare
|
||||
# puts "preparing save"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# class ConfigStorage < Storage
|
||||
# before_save :saving_message
|
||||
# def saving_message
|
||||
# puts "saving..."
|
||||
# end
|
||||
#
|
||||
# after_save do |object|
|
||||
# puts "saved"
|
||||
# end
|
||||
#
|
||||
# def save
|
||||
# run_callbacks(:before_save)
|
||||
# puts "- save"
|
||||
# run_callbacks(:after_save)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# config = ConfigStorage.new
|
||||
# config.save
|
||||
#
|
||||
# Output:
|
||||
# preparing save
|
||||
# saving...
|
||||
# - save
|
||||
# saved
|
||||
module DeprecatedCallbacks
|
||||
class CallbackChain < Array
|
||||
def self.build(kind, *methods, &block)
|
||||
methods, options = extract_options(*methods, &block)
|
||||
methods.map! { |method| Callback.new(kind, method, options) }
|
||||
new(methods)
|
||||
end
|
||||
|
||||
def run(object, options = {}, &terminator)
|
||||
enumerator = options[:enumerator] || :each
|
||||
|
||||
unless block_given?
|
||||
send(enumerator) { |callback| callback.call(object) }
|
||||
else
|
||||
send(enumerator) do |callback|
|
||||
result = callback.call(object)
|
||||
break result if terminator.call(result, object)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Decompose into more Array like behavior
|
||||
def replace_or_append!(chain)
|
||||
if index = index(chain)
|
||||
self[index] = chain
|
||||
else
|
||||
self << chain
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def find(callback, &block)
|
||||
select { |c| c == callback && (!block_given? || yield(c)) }.first
|
||||
end
|
||||
|
||||
def delete(callback)
|
||||
super(callback.is_a?(Callback) ? callback : find(callback))
|
||||
end
|
||||
|
||||
private
|
||||
def self.extract_options(*methods, &block)
|
||||
methods.flatten!
|
||||
options = methods.extract_options!
|
||||
methods << block if block_given?
|
||||
return methods, options
|
||||
end
|
||||
|
||||
def extract_options(*methods, &block)
|
||||
self.class.extract_options(*methods, &block)
|
||||
end
|
||||
end
|
||||
|
||||
class Callback
|
||||
attr_reader :kind, :method, :identifier, :options
|
||||
|
||||
def initialize(kind, method, options = {})
|
||||
@kind = kind
|
||||
@method = method
|
||||
@identifier = options[:identifier]
|
||||
@options = options
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
case other
|
||||
when Callback
|
||||
(self.identifier && self.identifier == other.identifier) || self.method == other.method
|
||||
else
|
||||
(self.identifier && self.identifier == other) || self.method == other
|
||||
end
|
||||
end
|
||||
|
||||
def eql?(other)
|
||||
self == other
|
||||
end
|
||||
|
||||
def dup
|
||||
self.class.new(@kind, @method, @options.dup)
|
||||
end
|
||||
|
||||
def hash
|
||||
if @identifier
|
||||
@identifier.hash
|
||||
else
|
||||
@method.hash
|
||||
end
|
||||
end
|
||||
|
||||
def call(*args, &block)
|
||||
evaluate_method(method, *args, &block) if should_run_callback?(*args)
|
||||
rescue LocalJumpError
|
||||
raise ArgumentError,
|
||||
"Cannot yield from a Proc type filter. The Proc must take two " +
|
||||
"arguments and execute #call on the second argument."
|
||||
end
|
||||
|
||||
private
|
||||
def evaluate_method(method, *args, &block)
|
||||
case method
|
||||
when Symbol
|
||||
object = args.shift
|
||||
object.send(method, *args, &block)
|
||||
when String
|
||||
eval(method, args.first.instance_eval { binding })
|
||||
when Proc, Method
|
||||
method.call(*args, &block)
|
||||
else
|
||||
if method.respond_to?(kind)
|
||||
method.send(kind, *args, &block)
|
||||
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?(*args)
|
||||
Array.wrap(options[:if]).flatten.compact.all? { |a| evaluate_method(a, *args) } &&
|
||||
!Array.wrap(options[:unless]).flatten.compact.any? { |a| evaluate_method(a, *args) }
|
||||
end
|
||||
end
|
||||
|
||||
def self.included(base)
|
||||
base.extend ClassMethods
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def define_callbacks(*callbacks)
|
||||
ActiveSupport::Deprecation.warn('ActiveSupport::DeprecatedCallbacks has been deprecated in favor of ActiveSupport::Callbacks', caller)
|
||||
|
||||
callbacks.each do |callback|
|
||||
class_eval <<-"end_eval", __FILE__, __LINE__ + 1
|
||||
def self.#{callback}(*methods, &block) # def self.before_save(*methods, &block)
|
||||
callbacks = CallbackChain.build(:#{callback}, *methods, &block) # callbacks = CallbackChain.build(:before_save, *methods, &block)
|
||||
@#{callback}_callbacks ||= CallbackChain.new # @before_save_callbacks ||= CallbackChain.new
|
||||
@#{callback}_callbacks.concat callbacks # @before_save_callbacks.concat callbacks
|
||||
end # end
|
||||
#
|
||||
def self.#{callback}_callback_chain # def self.before_save_callback_chain
|
||||
@#{callback}_callbacks ||= CallbackChain.new # @before_save_callbacks ||= CallbackChain.new
|
||||
#
|
||||
if superclass.respond_to?(:#{callback}_callback_chain) # if superclass.respond_to?(:before_save_callback_chain)
|
||||
CallbackChain.new( # CallbackChain.new(
|
||||
superclass.#{callback}_callback_chain + # superclass.before_save_callback_chain +
|
||||
@#{callback}_callbacks # @before_save_callbacks
|
||||
) # )
|
||||
else # else
|
||||
@#{callback}_callbacks # @before_save_callbacks
|
||||
end # end
|
||||
end # end
|
||||
end_eval
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Runs all the callbacks defined for the given options.
|
||||
#
|
||||
# If a block is given it will be called after each callback receiving as arguments:
|
||||
#
|
||||
# * the result from the callback
|
||||
# * the object which has the callback
|
||||
#
|
||||
# If the result from the block evaluates to +true+, the callback chain is stopped.
|
||||
#
|
||||
# Example:
|
||||
# class Storage
|
||||
# include ActiveSupport::DeprecatedCallbacks
|
||||
#
|
||||
# define_callbacks :before_save, :after_save
|
||||
# end
|
||||
#
|
||||
# class ConfigStorage < Storage
|
||||
# before_save :pass
|
||||
# before_save :pass
|
||||
# before_save :stop
|
||||
# before_save :pass
|
||||
#
|
||||
# def pass
|
||||
# puts "pass"
|
||||
# end
|
||||
#
|
||||
# def stop
|
||||
# puts "stop"
|
||||
# return false
|
||||
# end
|
||||
#
|
||||
# def save
|
||||
# result = run_callbacks(:before_save) { |result, object| result == false }
|
||||
# puts "- save" if result
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# config = ConfigStorage.new
|
||||
# config.save
|
||||
#
|
||||
# Output:
|
||||
# pass
|
||||
# pass
|
||||
# stop
|
||||
def run_callbacks(kind, options = {}, &block)
|
||||
self.class.send("#{kind}_callback_chain").run(self, options, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,16 +1,26 @@
|
||||
module ActiveSupport
|
||||
module Testing
|
||||
module SetupAndTeardown
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
include ActiveSupport::DeprecatedCallbacks
|
||||
define_callbacks :setup, :teardown
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
if defined?(MiniTest::Assertions) && TestCase < MiniTest::Assertions
|
||||
include ForMiniTest
|
||||
else
|
||||
include ForClassicTestUnit
|
||||
end
|
||||
included do
|
||||
include ActiveSupport::Callbacks
|
||||
define_callbacks :setup, :teardown
|
||||
|
||||
if defined?(MiniTest::Assertions) && TestCase < MiniTest::Assertions
|
||||
include ForMiniTest
|
||||
else
|
||||
include ForClassicTestUnit
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def setup(*args, &block)
|
||||
set_callback(:setup, :before, *args, &block)
|
||||
end
|
||||
|
||||
def teardown(*args, &block)
|
||||
set_callback(:teardown, :after, *args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,13 +28,14 @@ module ActiveSupport
|
||||
def run(runner)
|
||||
result = '.'
|
||||
begin
|
||||
run_callbacks :setup
|
||||
result = super
|
||||
_run_setup_callbacks do
|
||||
result = super
|
||||
end
|
||||
rescue Exception => e
|
||||
result = runner.puke(self.class, method_name, e)
|
||||
ensure
|
||||
begin
|
||||
run_callbacks :teardown, :enumerator => :reverse_each
|
||||
_run_teardown_callbacks
|
||||
rescue Exception => e
|
||||
result = runner.puke(self.class, method_name, e)
|
||||
end
|
||||
@@ -42,23 +53,17 @@ module ActiveSupport
|
||||
def run(result)
|
||||
return if @method_name.to_s == "default_test"
|
||||
|
||||
if using_mocha = respond_to?(:mocha_verify)
|
||||
assertion_counter_klass = if defined?(Mocha::TestCaseAdapter::AssertionCounter)
|
||||
Mocha::TestCaseAdapter::AssertionCounter
|
||||
else
|
||||
Mocha::Integration::TestUnit::AssertionCounter
|
||||
end
|
||||
assertion_counter = assertion_counter_klass.new(result)
|
||||
end
|
||||
|
||||
mocha_counter = retrieve_mocha_counter(result)
|
||||
yield(Test::Unit::TestCase::STARTED, name)
|
||||
@_result = result
|
||||
|
||||
begin
|
||||
begin
|
||||
run_callbacks :setup
|
||||
setup
|
||||
__send__(@method_name)
|
||||
mocha_verify(assertion_counter) if using_mocha
|
||||
_run_setup_callbacks do
|
||||
setup
|
||||
__send__(@method_name)
|
||||
mocha_verify(mocha_counter) if mocha_counter
|
||||
end
|
||||
rescue Mocha::ExpectationError => e
|
||||
add_failure(e.message, e.backtrace)
|
||||
rescue Test::Unit::AssertionFailedError => e
|
||||
@@ -69,7 +74,7 @@ module ActiveSupport
|
||||
ensure
|
||||
begin
|
||||
teardown
|
||||
run_callbacks :teardown, :enumerator => :reverse_each
|
||||
_run_teardown_callbacks
|
||||
rescue Test::Unit::AssertionFailedError => e
|
||||
add_failure(e.message, e.backtrace)
|
||||
rescue Exception => e
|
||||
@@ -78,12 +83,26 @@ module ActiveSupport
|
||||
end
|
||||
end
|
||||
ensure
|
||||
mocha_teardown if using_mocha
|
||||
mocha_teardown if mocha_counter
|
||||
end
|
||||
|
||||
result.add_run
|
||||
yield(Test::Unit::TestCase::FINISHED, name)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def retrieve_mocha_counter(result) #:nodoc:
|
||||
if using_mocha = respond_to?(:mocha_verify)
|
||||
if defined?(Mocha::TestCaseAdapter::AssertionCounter)
|
||||
Mocha::TestCaseAdapter::AssertionCounter.new(result)
|
||||
else
|
||||
Mocha::Integration::TestUnit::AssertionCounter.new(result)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -102,9 +102,9 @@ class SetupAndTeardownTest < ActiveSupport::TestCase
|
||||
teardown :foo, :sentinel, :foo
|
||||
|
||||
def test_inherited_setup_callbacks
|
||||
assert_equal [:reset_callback_record, :foo], self.class.setup_callback_chain.map(&:method)
|
||||
assert_equal [:reset_callback_record, :foo], self.class._setup_callbacks.map(&:raw_filter)
|
||||
assert_equal [:foo], @called_back
|
||||
assert_equal [:foo, :sentinel, :foo], self.class.teardown_callback_chain.map(&:method)
|
||||
assert_equal [:foo, :sentinel, :foo], self.class._teardown_callbacks.map(&:raw_filter)
|
||||
end
|
||||
|
||||
def setup
|
||||
@@ -114,6 +114,7 @@ class SetupAndTeardownTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def reset_callback_record
|
||||
@called_back = []
|
||||
end
|
||||
@@ -133,9 +134,9 @@ class SubclassSetupAndTeardownTest < SetupAndTeardownTest
|
||||
teardown :bar
|
||||
|
||||
def test_inherited_setup_callbacks
|
||||
assert_equal [:reset_callback_record, :foo, :bar], self.class.setup_callback_chain.map(&:method)
|
||||
assert_equal [:reset_callback_record, :foo, :bar], self.class._setup_callbacks.map(&:raw_filter)
|
||||
assert_equal [:foo, :bar], @called_back
|
||||
assert_equal [:foo, :sentinel, :foo, :bar], self.class.teardown_callback_chain.map(&:method)
|
||||
assert_equal [:foo, :sentinel, :foo, :bar], self.class._teardown_callbacks.map(&:raw_filter)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
Reference in New Issue
Block a user