mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
d66e7835be
The current code base is not uniform. After some discussion, we have chosen to go with double quotes by default.
165 lines
5.5 KiB
Ruby
165 lines
5.5 KiB
Ruby
require "active_support/concern"
|
|
require "active_support/core_ext/class/attribute"
|
|
require "active_support/core_ext/string/inflections"
|
|
|
|
module ActiveSupport
|
|
# Rescuable module adds support for easier exception handling.
|
|
module Rescuable
|
|
extend Concern
|
|
|
|
included do
|
|
class_attribute :rescue_handlers
|
|
self.rescue_handlers = []
|
|
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
|
|
#
|
|
# Exceptions raised inside exception handlers are not propagated up.
|
|
def rescue_from(*klasses, with: nil, &block)
|
|
unless with
|
|
if block_given?
|
|
with = block
|
|
else
|
|
raise ArgumentError, "Need a handler. Pass the with: keyword argument or provide a block."
|
|
end
|
|
end
|
|
|
|
klasses.each do |klass|
|
|
key = if klass.is_a?(Module) && klass.respond_to?(:===)
|
|
klass.name
|
|
elsif klass.is_a?(String)
|
|
klass
|
|
else
|
|
raise ArgumentError, "#{klass.inspect} must be an Exception class or a String referencing an Exception class"
|
|
end
|
|
|
|
# Put the new handler at the end because the list is read in reverse.
|
|
self.rescue_handlers += [[key, with]]
|
|
end
|
|
end
|
|
|
|
# Matches an exception to a handler based on the exception class.
|
|
#
|
|
# If no handler matches the exception, check for a handler matching the
|
|
# (optional) exception.cause. If no handler matches the exception or its
|
|
# cause, this returns nil so you can deal with unhandled exceptions.
|
|
# Be sure to re-raise unhandled exceptions if this is what you expect.
|
|
#
|
|
# begin
|
|
# …
|
|
# rescue => exception
|
|
# rescue_with_handler(exception) || raise
|
|
# end
|
|
#
|
|
# Returns the exception if it was handled and nil if it was not.
|
|
def rescue_with_handler(exception, object: self)
|
|
if handler = handler_for_rescue(exception, object: object)
|
|
handler.call exception
|
|
exception
|
|
end
|
|
end
|
|
|
|
def handler_for_rescue(exception, object: self) #:nodoc:
|
|
case rescuer = find_rescue_handler(exception)
|
|
when Symbol
|
|
method = object.method(rescuer)
|
|
if method.arity == 0
|
|
-> e { method.call }
|
|
else
|
|
method
|
|
end
|
|
when Proc
|
|
if rescuer.arity == 0
|
|
-> e { object.instance_exec(&rescuer) }
|
|
else
|
|
-> e { object.instance_exec(e, &rescuer) }
|
|
end
|
|
end
|
|
end
|
|
|
|
private
|
|
def find_rescue_handler(exception)
|
|
if exception
|
|
# Handlers are in order of declaration but the most recently declared
|
|
# is the highest priority match, so we search for matching handlers
|
|
# in reverse.
|
|
_, handler = rescue_handlers.reverse_each.detect do |class_or_name, _|
|
|
if klass = constantize_rescue_handler_class(class_or_name)
|
|
klass === exception
|
|
end
|
|
end
|
|
|
|
handler || find_rescue_handler(exception.cause)
|
|
end
|
|
end
|
|
|
|
def constantize_rescue_handler_class(class_or_name)
|
|
case class_or_name
|
|
when String, Symbol
|
|
begin
|
|
# Try a lexical lookup first since we support
|
|
#
|
|
# class Super
|
|
# rescue_from 'Error', with: …
|
|
# end
|
|
#
|
|
# class Sub
|
|
# class Error < StandardError; end
|
|
# end
|
|
#
|
|
# so an Error raised in Sub will hit the 'Error' handler.
|
|
const_get class_or_name
|
|
rescue NameError
|
|
class_or_name.safe_constantize
|
|
end
|
|
else
|
|
class_or_name
|
|
end
|
|
end
|
|
end
|
|
|
|
# Delegates to the class method, but uses the instance as the subject for
|
|
# rescue_from handlers (method calls, instance_exec blocks).
|
|
def rescue_with_handler(exception)
|
|
self.class.rescue_with_handler exception, object: self
|
|
end
|
|
|
|
# Internal handler lookup. Delegates to class method. Some libraries call
|
|
# this directly, so keeping it around for compatibility.
|
|
def handler_for_rescue(exception) #:nodoc:
|
|
self.class.handler_for_rescue exception, object: self
|
|
end
|
|
end
|
|
end
|