From aae37bb4f7edd6a1820e420a60560369c6064f33 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sat, 19 Jan 2008 02:44:45 +0000 Subject: 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 --- activesupport/CHANGELOG | 2 + activesupport/lib/active_support.rb | 1 + activesupport/lib/active_support/callbacks.rb | 94 +++++++++++++++++++++ .../active_support/testing/setup_and_teardown.rb | 50 +---------- activesupport/test/callbacks_test.rb | 96 ++++++++++++++++++++++ activesupport/test/test_test.rb | 8 +- 6 files changed, 201 insertions(+), 50 deletions(-) create mode 100644 activesupport/lib/active_support/callbacks.rb create mode 100644 activesupport/test/callbacks_test.rb (limited to 'activesupport') diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index db46ce4f4d..5cd4a483e4 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -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] diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index c4b7cc8cba..7a0476b729 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -26,6 +26,7 @@ $:.unshift(File.dirname(__FILE__)) require 'active_support/vendor' require 'active_support/basic_object' require 'active_support/inflector' +require 'active_support/callbacks' require 'active_support/core_ext' diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb new file mode 100644 index 0000000000..ac9f1a9d5f --- /dev/null +++ b/activesupport/lib/active_support/callbacks.rb @@ -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 diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb index 1639462fae..b2a937edd0 100644 --- a/activesupport/lib/active_support/testing/setup_and_teardown.rb +++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb @@ -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 @@ module ActiveSupport 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 @@ module ActiveSupport 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 @@ module ActiveSupport 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 @@ module ActiveSupport 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 diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb new file mode 100644 index 0000000000..6d390bbc5c --- /dev/null +++ b/activesupport/test/callbacks_test.rb @@ -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 diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb index 88b505e59c..1e75e18602 100644 --- a/activesupport/test/test_test.rb +++ b/activesupport/test/test_test.rb @@ -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 -- cgit v1.2.3