Permalink
Browse files

Add tests for ActiveSupport::Rescuable. Use ActiveSupport::Rescuable …

…in ActionController::Base.
  • Loading branch information...
1 parent 964dfc1 commit 259a7a844b53b7d508145cc61fed9e11581e5409 @lifo lifo committed Oct 4, 2008
@@ -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.
- #
- # <tt>rescue_from</tt> receives a series of exception classes or class
- # names, and a trailing <tt>:with</tt> 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 <tt>exception.is_a?(klass)</tt> 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)
@@ -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.
@@ -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')
@@ -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.
+ #
+ # <tt>rescue_from</tt> receives a series of exception classes or class
+ # names, and a trailing <tt>:with</tt> 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 <tt>exception.is_a?(klass)</tt> 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
@@ -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

0 comments on commit 259a7a8

Please sign in to comment.