1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
# frozen_string_literal: true
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, default: []
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
#
# private
# 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, visited_exceptions: [])
visited_exceptions << exception
if handler = handler_for_rescue(exception, object: object)
handler.call exception
exception
elsif exception
if visited_exceptions.include?(exception.cause)
nil
else
rescue_with_handler(exception.cause, object: object, visited_exceptions: visited_exceptions)
end
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
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
|