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:
parent
964dfc1557
commit
259a7a844b
5 changed files with 136 additions and 119 deletions
|
@ -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)
|
||||
|
|
|
@ -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/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')
|
||||
|
|
|
@ -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
|
||||
|
|
75
activesupport/test/rescuable_test.rb
Normal file
75
activesupport/test/rescuable_test.rb
Normal 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
|
Loading…
Reference in a new issue