109 lines
4.0 KiB
Ruby
109 lines
4.0 KiB
Ruby
module ActiveSupport
|
|
# Rescuable module adds support for easier exception handling.
|
|
module Rescuable
|
|
def self.included(base) # :nodoc:
|
|
base.class_inheritable_accessor :rescue_handlers
|
|
base.rescue_handlers = []
|
|
|
|
base.extend(ClassMethods)
|
|
end
|
|
|
|
module ClassMethods
|
|
# 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
|
|
else
|
|
raise ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument."
|
|
end
|
|
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
|
|
|
|
# put the new handler at the end because the list is read in reverse
|
|
rescue_handlers << [key, options[:with]]
|
|
end
|
|
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
|
|
true # don't rely on the return value of the handler
|
|
end
|
|
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.
|
|
_, rescuer = 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
|
|
end
|
|
|
|
case rescuer
|
|
when Symbol
|
|
method(rescuer)
|
|
when Proc
|
|
rescuer.bind(self)
|
|
end
|
|
end
|
|
end
|
|
end
|