1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Add tests for ActiveSupport::Rescuable. Use ActiveSupport::Rescuable in ActionController::Base.

This commit is contained in:
Pratik Naik 2008-10-04 21:48:18 +01:00
parent 964dfc1557
commit 259a7a844b
5 changed files with 136 additions and 119 deletions

View file

@ -41,10 +41,9 @@ module ActionController #: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 ActionController #:nodoc:
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 @@ module ActionController #:nodoc:
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 @@ module ActionController #:nodoc:
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)

View file

@ -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.

View file

@ -56,6 +56,8 @@ require 'active_support/time_with_zone'
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')

View file

@ -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 @@ module ActiveSupport
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

View file

@ -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