2017-07-09 08:06:36 -04:00
|
|
|
# frozen_string_literal: true
|
2017-07-10 09:39:13 -04:00
|
|
|
|
2017-10-21 09:11:29 -04:00
|
|
|
require "active_support/concern"
|
|
|
|
require "active_support/core_ext/class/attribute"
|
|
|
|
require "active_support/core_ext/string/inflections"
|
2009-03-24 22:46:42 -04:00
|
|
|
|
2008-10-04 15:28:08 -04:00
|
|
|
module ActiveSupport
|
2008-10-04 16:48:18 -04:00
|
|
|
# Rescuable module adds support for easier exception handling.
|
2008-10-04 15:28:08 -04:00
|
|
|
module Rescuable
|
2009-10-14 00:30:06 -04:00
|
|
|
extend Concern
|
2008-10-04 16:48:18 -04:00
|
|
|
|
2009-10-14 00:30:06 -04:00
|
|
|
included do
|
2017-05-29 12:01:50 -04:00
|
|
|
class_attribute :rescue_handlers, default: []
|
2008-10-04 15:28:08 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
module ClassMethods
|
2020-12-30 11:48:30 -05:00
|
|
|
# Registers exception classes with a handler to be called by <tt>rescue_with_handler</tt>.
|
2008-10-04 16:48:18 -04:00
|
|
|
#
|
|
|
|
# <tt>rescue_from</tt> receives a series of exception classes or class
|
2020-12-30 11:48:30 -05:00
|
|
|
# names, and an exception handler specified by a trailing <tt>:with</tt>
|
|
|
|
# option containing the name of a method or a Proc object. Alternatively, a block
|
|
|
|
# can be given as the handler.
|
2008-10-04 16:48:18 -04:00
|
|
|
#
|
|
|
|
# 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
|
2012-09-12 14:46:33 -04:00
|
|
|
# rescue_from User::NotAuthorized, with: :deny_access # self defined exception
|
|
|
|
# rescue_from ActiveRecord::RecordInvalid, with: :show_errors
|
2008-10-04 16:48:18 -04:00
|
|
|
#
|
|
|
|
# rescue_from 'MyAppError::Base' do |exception|
|
2012-09-12 14:46:33 -04:00
|
|
|
# render xml: exception, status: 500
|
2008-10-04 16:48:18 -04:00
|
|
|
# end
|
|
|
|
#
|
2016-12-17 03:13:50 -05:00
|
|
|
# private
|
2008-10-04 16:48:18 -04:00
|
|
|
# def deny_access
|
|
|
|
# ...
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# def show_errors(exception)
|
|
|
|
# exception.record.new_record? ? ...
|
|
|
|
# end
|
|
|
|
# end
|
2010-08-14 11:52:52 -04:00
|
|
|
#
|
2012-04-29 01:32:18 -04:00
|
|
|
# Exceptions raised inside exception handlers are not propagated up.
|
2016-05-13 20:43:48 -04:00
|
|
|
def rescue_from(*klasses, with: nil, &block)
|
|
|
|
unless with
|
2008-10-04 15:28:08 -04:00
|
|
|
if block_given?
|
2016-05-13 20:43:48 -04:00
|
|
|
with = block
|
2008-10-04 15:28:08 -04:00
|
|
|
else
|
2016-08-06 11:58:50 -04:00
|
|
|
raise ArgumentError, "Need a handler. Pass the with: keyword argument or provide a block."
|
2008-10-04 15:28:08 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
klasses.each do |klass|
|
2014-12-09 05:04:50 -05:00
|
|
|
key = if klass.is_a?(Module) && klass.respond_to?(:===)
|
2008-10-04 15:28:08 -04:00
|
|
|
klass.name
|
|
|
|
elsif klass.is_a?(String)
|
|
|
|
klass
|
|
|
|
else
|
2016-05-13 20:43:48 -04:00
|
|
|
raise ArgumentError, "#{klass.inspect} must be an Exception class or a String referencing an Exception class"
|
2008-10-04 15:28:08 -04:00
|
|
|
end
|
|
|
|
|
2015-04-26 15:38:52 -04:00
|
|
|
# Put the new handler at the end because the list is read in reverse.
|
2016-05-13 20:43:48 -04:00
|
|
|
self.rescue_handlers += [[key, with]]
|
2008-10-04 15:28:08 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-05-13 20:43:48 -04:00
|
|
|
# Matches an exception to a handler based on the exception class.
|
|
|
|
#
|
|
|
|
# If no handler matches the exception, check for a handler matching the
|
2022-02-18 17:11:39 -05:00
|
|
|
# (optional) +exception.cause+. If no handler matches the exception or its
|
2016-10-26 17:13:15 -04:00
|
|
|
# cause, this returns +nil+, so you can deal with unhandled exceptions.
|
2016-05-13 20:43:48 -04:00
|
|
|
# Be sure to re-raise unhandled exceptions if this is what you expect.
|
|
|
|
#
|
|
|
|
# begin
|
|
|
|
# …
|
|
|
|
# rescue => exception
|
|
|
|
# rescue_with_handler(exception) || raise
|
|
|
|
# end
|
|
|
|
#
|
2016-10-26 17:13:15 -04:00
|
|
|
# Returns the exception if it was handled and +nil+ if it was not.
|
2017-05-10 13:30:41 -04:00
|
|
|
def rescue_with_handler(exception, object: self, visited_exceptions: [])
|
|
|
|
visited_exceptions << exception
|
|
|
|
|
2016-11-17 15:03:35 -05:00
|
|
|
if handler = handler_for_rescue(exception, object: object)
|
2016-05-13 20:43:48 -04:00
|
|
|
handler.call exception
|
|
|
|
exception
|
2016-11-17 15:03:35 -05:00
|
|
|
elsif exception
|
2017-05-10 13:30:41 -04:00
|
|
|
if visited_exceptions.include?(exception.cause)
|
|
|
|
nil
|
|
|
|
else
|
|
|
|
rescue_with_handler(exception.cause, object: object, visited_exceptions: visited_exceptions)
|
|
|
|
end
|
2016-05-13 20:43:48 -04:00
|
|
|
end
|
2008-10-04 15:28:08 -04:00
|
|
|
end
|
|
|
|
|
2021-07-29 17:18:07 -04:00
|
|
|
def handler_for_rescue(exception, object: self) # :nodoc:
|
2016-11-17 15:03:35 -05:00
|
|
|
case rescuer = find_rescue_handler(exception)
|
2016-05-13 20:43:48 -04:00
|
|
|
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
|
2008-10-04 15:28:08 -04:00
|
|
|
end
|
|
|
|
|
2016-05-13 20:43:48 -04:00
|
|
|
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
|
|
|
|
|
2016-11-17 15:03:35 -05:00
|
|
|
handler
|
2016-05-13 20:43:48 -04:00
|
|
|
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
|
2012-03-23 06:49:25 -04:00
|
|
|
end
|
2008-10-04 15:28:08 -04:00
|
|
|
end
|
2016-03-11 12:14:50 -05:00
|
|
|
|
2016-05-13 20:43:48 -04:00
|
|
|
# Delegates to the class method, but uses the instance as the subject for
|
2022-02-18 17:11:39 -05:00
|
|
|
# rescue_from handlers (method calls, +instance_exec+ blocks).
|
2016-05-13 20:43:48 -04:00
|
|
|
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.
|
2021-07-29 17:18:07 -04:00
|
|
|
def handler_for_rescue(exception) # :nodoc:
|
2016-05-13 20:43:48 -04:00
|
|
|
self.class.handler_for_rescue exception, object: self
|
2016-03-11 12:14:50 -05:00
|
|
|
end
|
2008-10-04 15:28:08 -04:00
|
|
|
end
|
|
|
|
end
|