diff --git a/actionpack/lib/action_controller/rescue.rb b/actionpack/lib/action_controller/rescue.rb index 0e0fe15fdb17c6ad3c65e2132875e590ece0e810..ec8e9b92d5c879c97a2e0bd2f3944bf8a9c33306 100644 --- a/actionpack/lib/action_controller/rescue.rb +++ b/actionpack/lib/action_controller/rescue.rb @@ -41,10 +41,9 @@ def self.included(base) #:nodoc: base.rescue_templates = Hash.new(DEFAULT_RESCUE_TEMPLATE) base.rescue_templates.update DEFAULT_RESCUE_TEMPLATES - base.class_inheritable_array :rescue_handlers - base.rescue_handlers = [] - base.extend(ClassMethods) + base.send :include, ActiveSupport::Rescuable + base.class_eval do alias_method_chain :perform_action, :rescue end @@ -54,65 +53,12 @@ module ClassMethods def process_with_exception(request, response, exception) #:nodoc: new.process(request, response, :rescue_action, exception) end - - # Rescue exceptions raised in controller actions. - # - # rescue_from receives a series of exception classes or class - # names, and a trailing :with option with the name of a method - # or a Proc object to be called to handle them. Alternatively a block can - # be given. - # - # Handlers that take one argument will be called with the exception, so - # that the exception can be inspected when dealing with it. - # - # Handlers are inherited. They are searched from right to left, from - # bottom to top, and up the hierarchy. The handler of the first class for - # which exception.is_a?(klass) holds true is the one invoked, if - # any. - # - # class ApplicationController < ActionController::Base - # rescue_from User::NotAuthorized, :with => :deny_access # self defined exception - # rescue_from ActiveRecord::RecordInvalid, :with => :show_errors - # - # rescue_from 'MyAppError::Base' do |exception| - # render :xml => exception, :status => 500 - # end - # - # protected - # def deny_access - # ... - # end - # - # def show_errors(exception) - # exception.record.new_record? ? ... - # end - # end - def rescue_from(*klasses, &block) - options = klasses.extract_options! - unless options.has_key?(:with) - block_given? ? options[:with] = block : raise(ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument.") - end - - klasses.each do |klass| - key = if klass.is_a?(Class) && klass <= Exception - klass.name - elsif klass.is_a?(String) - klass - else - raise(ArgumentError, "#{klass} is neither an Exception nor a String") - end - - # Order is important, we put the pair at the end. When dealing with an - # exception we will follow the documented order going from right to left. - rescue_handlers << [key, options[:with]] - end - end end protected # Exception handler called when the performance of an action raises an exception. def rescue_action(exception) - rescue_action_with_handler(exception) || rescue_action_without_handler(exception) + rescue_with_handler(exception) || rescue_action_without_handler(exception) end # Overwrite to implement custom logging of errors. By default logs as fatal. @@ -168,18 +114,6 @@ def rescue_action_locally(exception) render_for_file(rescues_path("layout"), response_code_for_rescue(exception)) end - # Tries to rescue the exception by looking up and calling a registered handler. - def rescue_action_with_handler(exception) - if handler = handler_for_rescue(exception) - if handler.arity != 0 - handler.call(exception) - else - handler.call - end - true # don't rely on the return value of the handler - end - end - def rescue_action_without_handler(exception) log_error(exception) if logger erase_results if performed? @@ -216,36 +150,6 @@ def response_code_for_rescue(exception) rescue_responses[exception.class.name] end - def handler_for_rescue(exception) - # We go from right to left because pairs are pushed onto rescue_handlers - # as rescue_from declarations are found. - _, handler = *rescue_handlers.reverse.detect do |klass_name, handler| - # The purpose of allowing strings in rescue_from is to support the - # declaration of handler associations for exception classes whose - # definition is yet unknown. - # - # Since this loop needs the constants it would be inconsistent to - # assume they should exist at this point. An early raised exception - # could trigger some other handler and the array could include - # precisely a string whose corresponding constant has not yet been - # seen. This is why we are tolerant to unknown constants. - # - # Note that this tolerance only matters if the exception was given as - # a string, otherwise a NameError will be raised by the interpreter - # itself when rescue_from CONSTANT is executed. - klass = self.class.const_get(klass_name) rescue nil - klass ||= klass_name.constantize rescue nil - exception.is_a?(klass) if klass - end - - case handler - when Symbol - method(handler) - when Proc - handler.bind(self) - end - end - def clean_backtrace(exception) if backtrace = exception.backtrace if defined?(RAILS_ROOT) diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 9700a11531f3e9e42fadab92dfdb42199fe154ad..2c6f4ed582cd25a7a8005cb6e8dd380ed6da6343 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* Add ActiveSupport::Rescuable module abstracting ActionController::Base rescue_from features. [Norbert Crombach, Pratik] + * Switch from String#chars to String#mb_chars for the unicode proxy. [Manfred Stienstra] This helps with 1.8.7 compatibility and also improves performance for some operations by reducing indirection. diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index b30faff06ddbd141cc832b494964dd39163d97fb..0ff09067ecd789bb534fddd1f05060bdb3e449b4 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -56,6 +56,8 @@ require 'active_support/secure_random' +require 'active_support/rescuable' + I18n.load_path << File.dirname(__FILE__) + '/active_support/locale/en-US.yml' Inflector = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Inflector', 'ActiveSupport::Inflector') diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb index d1a43666366063154e06c2a54d0f6e09790d7d97..f2bc12e832d26a101387af86b6f47efdd11afa80 100644 --- a/activesupport/lib/active_support/rescuable.rb +++ b/activesupport/lib/active_support/rescuable.rb @@ -1,28 +1,49 @@ module ActiveSupport + # Rescuable module adds support for easier exception handling. module Rescuable def self.included(base) # :nodoc: - base.class_inheritable_array :rescue_handlers + base.class_inheritable_accessor :rescue_handlers base.rescue_handlers = [] + base.extend(ClassMethods) end module ClassMethods - def enable_rescue_for(*methods) - methods.each do |method| - class_eval <<-EOS - def #{method}_with_rescue(*args, &block) - #{method}_without_rescue(*args, &block) - rescue Exception => exception - rescue_with_handler(exception) - end - - alias_method_chain :#{method}, :rescue - EOS - end - end - + # Rescue exceptions raised in controller actions. + # + # rescue_from receives a series of exception classes or class + # names, and a trailing :with option with the name of a method + # or a Proc object to be called to handle them. Alternatively a block can + # be given. + # + # Handlers that take one argument will be called with the exception, so + # that the exception can be inspected when dealing with it. + # + # Handlers are inherited. They are searched from right to left, from + # bottom to top, and up the hierarchy. The handler of the first class for + # which exception.is_a?(klass) holds true is the one invoked, if + # any. + # + # class ApplicationController < ActionController::Base + # rescue_from User::NotAuthorized, :with => :deny_access # self defined exception + # rescue_from ActiveRecord::RecordInvalid, :with => :show_errors + # + # rescue_from 'MyAppError::Base' do |exception| + # render :xml => exception, :status => 500 + # end + # + # protected + # def deny_access + # ... + # end + # + # def show_errors(exception) + # exception.record.new_record? ? ... + # end + # end def rescue_from(*klasses, &block) options = klasses.extract_options! + unless options.has_key?(:with) if block_given? options[:with] = block @@ -46,18 +67,31 @@ def rescue_from(*klasses, &block) end end + # Tries to rescue the exception by looking up and calling a registered handler. def rescue_with_handler(exception) if handler = handler_for_rescue(exception) handler.arity != 0 ? handler.call(exception) : handler.call - else - raise exception + true # don't rely on the return value of the handler end end def handler_for_rescue(exception) - # use reverse so what is added last is found first - _, handler = *rescue_handlers.reverse.detect do |klass_name, handler| - # allow strings to support constants that are not defined yet + # We go from right to left because pairs are pushed onto rescue_handlers + # as rescue_from declarations are found. + _, handler = Array(rescue_handlers).reverse.detect do |klass_name, handler| + # The purpose of allowing strings in rescue_from is to support the + # declaration of handler associations for exception classes whose + # definition is yet unknown. + # + # Since this loop needs the constants it would be inconsistent to + # assume they should exist at this point. An early raised exception + # could trigger some other handler and the array could include + # precisely a string whose corresponding constant has not yet been + # seen. This is why we are tolerant to unknown constants. + # + # Note that this tolerance only matters if the exception was given as + # a string, otherwise a NameError will be raised by the interpreter + # itself when rescue_from CONSTANT is executed. klass = self.class.const_get(klass_name) rescue nil klass ||= klass_name.constantize rescue nil exception.is_a?(klass) if klass diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..9f2b783b2ef34debec2d9383e985ef021d714bad --- /dev/null +++ b/activesupport/test/rescuable_test.rb @@ -0,0 +1,75 @@ +require 'abstract_unit' + +class WraithAttack < StandardError +end + +class NuclearExplosion < StandardError +end + +class MadRonon < StandardError + attr_accessor :message + + def initialize(message) + @message = message + super() + end +end + +class Stargate + attr_accessor :result + + include ActiveSupport::Rescuable + + rescue_from WraithAttack, :with => :sos + + rescue_from NuclearExplosion do + @result = 'alldead' + end + + rescue_from MadRonon do |e| + @result = e.message + end + + def dispatch(method) + send(method) + rescue Exception => e + rescue_with_handler(e) + end + + def attack + raise WraithAttack + end + + def nuke + raise NuclearExplosion + end + + def ronanize + raise MadRonon.new("dex") + end + + def sos + @result = 'killed' + end +end + +class RescueableTest < Test::Unit::TestCase + def setup + @stargate = Stargate.new + end + + def test_rescue_from_with_method + @stargate.dispatch :attack + assert_equal 'killed', @stargate.result + end + + def test_rescue_from_with_block + @stargate.dispatch :nuke + assert_equal 'alldead', @stargate.result + end + + def test_rescue_from_with_block_with_args + @stargate.dispatch :ronanize + assert_equal 'dex', @stargate.result + end +end