reactive-mvc 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +21 -0
- data/Manifest +34 -0
- data/README +30 -0
- data/Rakefile +5 -0
- data/lib/reactive-mvc.rb +26 -0
- data/lib/reactive-mvc/controller.rb +16 -0
- data/lib/reactive-mvc/controller/base.rb +405 -0
- data/lib/reactive-mvc/controller/filters.rb +767 -0
- data/lib/reactive-mvc/controller/flash.rb +161 -0
- data/lib/reactive-mvc/controller/helpers.rb +203 -0
- data/lib/reactive-mvc/controller/layout.rb +285 -0
- data/lib/reactive-mvc/controller/output.rb +262 -0
- data/lib/reactive-mvc/controller/rescue.rb +208 -0
- data/lib/reactive-mvc/dispatcher.rb +133 -0
- data/lib/reactive-mvc/view.rb +18 -0
- data/lib/reactive-mvc/view/base.rb +388 -0
- data/lib/reactive-mvc/view/helpers.rb +38 -0
- data/lib/reactive-mvc/view/partials.rb +207 -0
- data/lib/reactive-mvc/view/paths.rb +125 -0
- data/lib/reactive-mvc/view/renderable.rb +98 -0
- data/lib/reactive-mvc/view/renderable_partial.rb +49 -0
- data/lib/reactive-mvc/view/template.rb +110 -0
- data/lib/reactive-mvc/view/template_error.rb +9 -0
- data/lib/reactive-mvc/view/template_handler.rb +9 -0
- data/lib/reactive-mvc/view/template_handlers.rb +43 -0
- data/lib/reactive-mvc/view/template_handlers/builder.rb +15 -0
- data/lib/reactive-mvc/view/template_handlers/erb.rb +20 -0
- data/lib/reactive-mvc/view/template_handlers/ruby_code.rb +9 -0
- data/reactive_app_generators/mvc/USAGE +10 -0
- data/reactive_app_generators/mvc/mvc_generator.rb +48 -0
- data/reactive_app_generators/mvc/templates/application_controller.rb +2 -0
- data/reactive_generators/controller/USAGE +7 -0
- data/reactive_generators/controller/controller_generator.rb +24 -0
- data/reactive_generators/controller/templates/controller.rb +2 -0
- metadata +113 -0
@@ -0,0 +1,262 @@
|
|
1
|
+
module Reactive::Mvc::Controller #:nodoc:
|
2
|
+
class InvalidOption < Error
|
3
|
+
end
|
4
|
+
|
5
|
+
module Output #:nodoc:
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
base.class_eval { include InstanceMethods }
|
9
|
+
end
|
10
|
+
|
11
|
+
# output :pdf, :show => {:treatment => 'print', :handler => 'system_viewer'}
|
12
|
+
# output :pdf, :show => 'print' # same as above but without the handler (the available handler will be used (like nil in :handler))
|
13
|
+
# output :pdf, :treatment => 'print', :handler => 'system_viewer' # all actions will have this settings (like :all => {})
|
14
|
+
# output :pdf, 'print' # all actions will have this settings (with a nil handler) (like :all => :print}
|
15
|
+
|
16
|
+
# output :pdf, [:show, :index, :new] => {:treatment => 'print', :handler => 'system_viewer'}, :delete => 'display'
|
17
|
+
# output :pdf, :all => {:treatment => 'print', :handler => 'system_viewer'}, :delete => 'display' # acts like all but delete (a bit like except for layout)
|
18
|
+
# output :pdf, :all => false, :delete => 'display' # acts like all: disable handling but delete
|
19
|
+
# output :pdf, :all => {:treatment => 'print', :handler => 'system_viewer'}, :delete => :depends # delete will call a method on the controller named depends that have to return the options
|
20
|
+
|
21
|
+
# By default, the settings are inherited from the parent controller, thus settings like: "output :pdf, :delete => :display" will only override the settings
|
22
|
+
# for delete. If you specify "output :pdf, :all => :print" it will override what was inherited.
|
23
|
+
|
24
|
+
# There may be several "output" statement per format but it is not recommended. If you do, the statment will be applied in the order they appear, so a last statement
|
25
|
+
# with :all => {} will simply override any previous ones, beware!
|
26
|
+
|
27
|
+
# Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
|
28
|
+
# repeated setups. The inclusion pattern has pages that look like this:
|
29
|
+
#
|
30
|
+
# <%= render "shared/header" %>
|
31
|
+
# Hello World
|
32
|
+
# <%= render "shared/footer" %>
|
33
|
+
#
|
34
|
+
# This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
|
35
|
+
# and if you ever want to change the structure of these two includes, you'll have to change all the templates.
|
36
|
+
#
|
37
|
+
# With layouts, you can flip it around and have the common structure know where to insert changing content. This means
|
38
|
+
# that the header and footer are only mentioned in one place, like this:
|
39
|
+
#
|
40
|
+
# // The header part of this layout
|
41
|
+
# <%= yield %>
|
42
|
+
# // The footer part of this layout -->
|
43
|
+
#
|
44
|
+
# And then you have content pages that look like this:
|
45
|
+
#
|
46
|
+
# hello world
|
47
|
+
#
|
48
|
+
# Not a word about common structures. At rendering time, the content page is computed and then inserted in the layout,
|
49
|
+
# like this:
|
50
|
+
#
|
51
|
+
# // The header part of this layout
|
52
|
+
# hello world
|
53
|
+
# // The footer part of this layout -->
|
54
|
+
#
|
55
|
+
# == Accessing shared variables
|
56
|
+
#
|
57
|
+
# Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with
|
58
|
+
# references that won't materialize before rendering time:
|
59
|
+
#
|
60
|
+
# <h1><%= @page_title %></h1>
|
61
|
+
# <%= yield %>
|
62
|
+
#
|
63
|
+
# ...and content pages that fulfill these references _at_ rendering time:
|
64
|
+
#
|
65
|
+
# <% @page_title = "Welcome" %>
|
66
|
+
# Off-world colonies offers you a chance to start a new life
|
67
|
+
#
|
68
|
+
# The result after rendering is:
|
69
|
+
#
|
70
|
+
# <h1>Welcome</h1>
|
71
|
+
# Off-world colonies offers you a chance to start a new life
|
72
|
+
#
|
73
|
+
# == Automatic layout assignment
|
74
|
+
#
|
75
|
+
# If there is a template in <tt>app/views/layouts/</tt> with the same name as the current controller then it will be automatically
|
76
|
+
# set as that controller's layout nless explicitly told otherwise. Say you have a WeblogController, for example. If a template named
|
77
|
+
# <tt>app/views/layouts/weblog.erb</tt> or <tt>app/views/layouts/weblog.builder</tt> exists then it will be automatically set as
|
78
|
+
# the layout for your WeblogController. You can create a layout with the name <tt>application.erb</tt> or <tt>application.builder</tt>
|
79
|
+
# and this will be set as the default controller if there is no layout with the same name as the current controller and there is
|
80
|
+
# no layout explicitly assigned with the +layout+ method. Nested controllers use the same folder structure for automatic layout.
|
81
|
+
# assignment. So an Admin::WeblogController will look for a template named <tt>app/views/layouts/admin/weblog.erb</tt>.
|
82
|
+
# Setting a layout explicitly will always override the automatic behaviour for the controller where the layout is set.
|
83
|
+
# Explicitly setting the layout in a parent class, though, will not override the child class's layout assignment if the child
|
84
|
+
# class has a layout with the same name.
|
85
|
+
#
|
86
|
+
# == Inheritance for layouts
|
87
|
+
#
|
88
|
+
# Layouts are shared downwards in the inheritance hierarchy, but not upwards. Examples:
|
89
|
+
#
|
90
|
+
# class BankController < ActionController::Base
|
91
|
+
# layout "bank_standard"
|
92
|
+
#
|
93
|
+
# class InformationController < BankController
|
94
|
+
#
|
95
|
+
# class VaultController < BankController
|
96
|
+
# layout :access_level_layout
|
97
|
+
#
|
98
|
+
# class EmployeeController < BankController
|
99
|
+
# layout nil
|
100
|
+
#
|
101
|
+
# The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites
|
102
|
+
# and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all.
|
103
|
+
#
|
104
|
+
# == Types of layouts
|
105
|
+
#
|
106
|
+
# Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
|
107
|
+
# you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
|
108
|
+
# be done either by specifying a method reference as a symbol or using an inline method (as a proc).
|
109
|
+
#
|
110
|
+
# The method reference is the preferred approach to variable layouts and is used like this:
|
111
|
+
#
|
112
|
+
# class WeblogController < ActionController::Base
|
113
|
+
# layout :writers_and_readers
|
114
|
+
#
|
115
|
+
# def index
|
116
|
+
# # fetching posts
|
117
|
+
# end
|
118
|
+
#
|
119
|
+
# private
|
120
|
+
# def writers_and_readers
|
121
|
+
# logged_in? ? "writer_layout" : "reader_layout"
|
122
|
+
# end
|
123
|
+
#
|
124
|
+
# Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
|
125
|
+
# is logged in or not.
|
126
|
+
#
|
127
|
+
# If you want to use an inline method, such as a proc, do something like this:
|
128
|
+
#
|
129
|
+
# class WeblogController < ActionController::Base
|
130
|
+
# layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
|
131
|
+
#
|
132
|
+
# Of course, the most common way of specifying a layout is still just as a plain template name:
|
133
|
+
#
|
134
|
+
# class WeblogController < ActionController::Base
|
135
|
+
# layout "weblog_standard"
|
136
|
+
#
|
137
|
+
# If no directory is specified for the template name, the template will by default be looked for in +app/views/layouts/+.
|
138
|
+
# Otherwise, it will be looked up relative to the template root.
|
139
|
+
#
|
140
|
+
# == Conditional layouts
|
141
|
+
#
|
142
|
+
# If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
|
143
|
+
# a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The
|
144
|
+
# <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example:
|
145
|
+
#
|
146
|
+
# class WeblogController < ActionController::Base
|
147
|
+
# layout "weblog_standard", :except => :rss
|
148
|
+
#
|
149
|
+
# # ...
|
150
|
+
#
|
151
|
+
# end
|
152
|
+
#
|
153
|
+
# This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout
|
154
|
+
# around the rendered view.
|
155
|
+
#
|
156
|
+
# Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
|
157
|
+
# #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
|
158
|
+
#
|
159
|
+
# == Using a different layout in the action render call
|
160
|
+
#
|
161
|
+
# If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
|
162
|
+
# Some times you'll have exceptions, though, where one action wants to use a different layout than the rest of the controller.
|
163
|
+
# This is possible using the <tt>render</tt> method. It's just a bit more manual work as you'll have to supply fully
|
164
|
+
# qualified template and layout names as this example shows:
|
165
|
+
#
|
166
|
+
# class WeblogController < ActionController::Base
|
167
|
+
# def help
|
168
|
+
# render :action => "help/index", :layout => "help"
|
169
|
+
# end
|
170
|
+
# end
|
171
|
+
#
|
172
|
+
# As you can see, you pass the template as the first parameter, the status code as the second ("200" is OK), and the layout
|
173
|
+
# as the third.
|
174
|
+
#
|
175
|
+
module ClassMethods
|
176
|
+
def output(format, conditions = {})
|
177
|
+
conditions = normalize_output_conditions(conditions)
|
178
|
+
if conditions[:all]
|
179
|
+
write_inheritable_attribute("output_#{format}", conditions)
|
180
|
+
else
|
181
|
+
write_inheritable_hash("output_#{format}", conditions)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def default_output(format, action) #:nodoc:
|
186
|
+
conditions = read_inheritable_attribute("output_#{format}") || {}
|
187
|
+
conditions[:all] || conditions[action.to_sym] || {}
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
def normalize_output_conditions(conditions)
|
192
|
+
normalized = if conditions.is_a?(Hash) && (conditions.has_key?(:handler) || conditions.has_key?(:treatment))
|
193
|
+
{:all => conditions}
|
194
|
+
elsif [NilClass, FalseClass, TrueClass, Symbol, Proc].find {|klass| conditions.is_a? klass}
|
195
|
+
{:all => conditions}
|
196
|
+
else
|
197
|
+
conditions
|
198
|
+
end
|
199
|
+
normalized.inject({}) {|hash, (key, value)| hash.merge(key => normalize_output_options(value))}
|
200
|
+
end
|
201
|
+
|
202
|
+
def normalize_output_options(options)
|
203
|
+
case options
|
204
|
+
when String
|
205
|
+
case (parts = options.split('/')).size
|
206
|
+
when 1 then {:handler => nil, :treatment => options}
|
207
|
+
when 2 then {:handler => parts.first, :treatment => parts.last}
|
208
|
+
else raise InvalidOption, "Output option '#{options}' contains several slashes, this is invalid!"
|
209
|
+
end
|
210
|
+
when Hash then options
|
211
|
+
when Symbol, Proc then options
|
212
|
+
when FalseClass then {:handler => false}
|
213
|
+
when NilClass, TrueClass then {:handler => nil}
|
214
|
+
else raise InvalidOption, "Output option '#{options}' isn't handled!"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
module InstanceMethods # :nodoc:
|
220
|
+
def self.included(base)
|
221
|
+
base.class_eval do
|
222
|
+
alias_method_chain :render, :output
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# Returns an output hash containing the name of the handler and the treatment to apply on output.
|
227
|
+
# If the output specs were specified as a method reference (through a symbol), this method is called and the return
|
228
|
+
# value is used. Likewise if the output specs were specified as an inline method (through a proc or method object).
|
229
|
+
def active_output(passed_output = nil)
|
230
|
+
output = passed_output || self.class.default_output(request.format, action_name)
|
231
|
+
case output
|
232
|
+
when Symbol then send(output)
|
233
|
+
when Proc then output.call(self)
|
234
|
+
else output
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
protected
|
239
|
+
|
240
|
+
def render_with_output(options = nil, &block)
|
241
|
+
set_output_info(options.is_a?(Hash) ? options[:output] : nil)
|
242
|
+
render_without_output(options, &block)
|
243
|
+
end
|
244
|
+
|
245
|
+
def set_output_info(options)
|
246
|
+
output = pick_output(options)
|
247
|
+
response.handler_name = output[:handler]
|
248
|
+
response.treatment = request.treatment || output[:treatment]
|
249
|
+
response.format = request.format
|
250
|
+
end
|
251
|
+
|
252
|
+
def pick_output(options)
|
253
|
+
if options.is_a?(Hash) && options.has_key?(:output)
|
254
|
+
active_output(self.class.normalize_output_options(options.delete(:output)))
|
255
|
+
else
|
256
|
+
active_output
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
module Reactive::Mvc::Controller #:nodoc:
|
2
|
+
# Actions that fail to perform as expected throw exceptions. These exceptions can either be rescued for the public view
|
3
|
+
# (with a nice user-friendly explanation) or for the developers view (with tons of debugging information). The developers view
|
4
|
+
# is already implemented by the Action Controller, but the public view should be tailored to your specific application.
|
5
|
+
#
|
6
|
+
# The default behavior for public exceptions is to render a static html file with the name of the error code thrown. If no such
|
7
|
+
# file exists, an empty response is sent with the correct status code.
|
8
|
+
#
|
9
|
+
# Custom rescue behavior is achieved by overriding the <tt>rescue_action</tt> method.
|
10
|
+
module Rescue
|
11
|
+
DEFAULT_RESCUE_RESPONSE = :internal_server_error
|
12
|
+
DEFAULT_RESCUE_RESPONSES = {
|
13
|
+
'Reactive::Mvc::Controller::UnknownAction' => :not_found,
|
14
|
+
'ActiveRecord::RecordNotFound' => :not_found,
|
15
|
+
'ActiveRecord::StaleObjectError' => :conflict,
|
16
|
+
'ActiveRecord::RecordInvalid' => :unprocessable_entity,
|
17
|
+
'ActiveRecord::RecordNotSaved' => :unprocessable_entity,
|
18
|
+
'Reactive::Mvc::Controller::MethodNotAllowed' => :method_not_allowed,
|
19
|
+
'Reactive::Mvc::Controller::NotImplemented' => :not_implemented,
|
20
|
+
}
|
21
|
+
|
22
|
+
DEFAULT_RESCUE_TEMPLATE = 'diagnostics'
|
23
|
+
DEFAULT_RESCUE_TEMPLATES = {
|
24
|
+
'Reactive::Mvc::View::MissingTemplate' => 'missing_template',
|
25
|
+
'Reactive::Mvc::Controller::UnknownAction' => 'unknown_action',
|
26
|
+
'Reactive::Mvc::View::TemplateError' => 'template_error'
|
27
|
+
}
|
28
|
+
|
29
|
+
def self.included(base) #:nodoc:
|
30
|
+
base.cattr_accessor :rescue_responses
|
31
|
+
base.rescue_responses = Hash.new(DEFAULT_RESCUE_RESPONSE)
|
32
|
+
base.rescue_responses.update DEFAULT_RESCUE_RESPONSES
|
33
|
+
|
34
|
+
base.cattr_accessor :rescue_templates
|
35
|
+
base.rescue_templates = Hash.new(DEFAULT_RESCUE_TEMPLATE)
|
36
|
+
base.rescue_templates.update DEFAULT_RESCUE_TEMPLATES
|
37
|
+
|
38
|
+
base.class_inheritable_array :rescue_handlers
|
39
|
+
base.rescue_handlers = []
|
40
|
+
|
41
|
+
base.extend(ClassMethods)
|
42
|
+
base.class_eval do
|
43
|
+
alias_method_chain :perform_action, :rescue
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module ClassMethods
|
48
|
+
def process_with_exception(request, response, exception) #:nodoc:
|
49
|
+
new.process(request, response, :rescue_the_action, exception)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Rescue exceptions raised in controller actions.
|
53
|
+
#
|
54
|
+
# <tt>rescue_from</tt> receives a series of exception classes or class
|
55
|
+
# names, and a trailing <tt>:with</tt> option with the name of a method
|
56
|
+
# or a Proc object to be called to handle them. Alternatively a block can
|
57
|
+
# be given.
|
58
|
+
#
|
59
|
+
# Handlers that take one argument will be called with the exception, so
|
60
|
+
# that the exception can be inspected when dealing with it.
|
61
|
+
#
|
62
|
+
# Handlers are inherited. They are searched from right to left, from
|
63
|
+
# bottom to top, and up the hierarchy. The handler of the first class for
|
64
|
+
# which <tt>exception.is_a?(klass)</tt> holds true is the one invoked, if
|
65
|
+
# any.
|
66
|
+
#
|
67
|
+
# class ApplicationController < ActionController::Base
|
68
|
+
# rescue_from User::NotAuthorized, :with => :deny_access # self defined exception
|
69
|
+
# rescue_from ActiveRecord::RecordInvalid, :with => :show_errors
|
70
|
+
#
|
71
|
+
# rescue_from 'MyAppError::Base' do |exception|
|
72
|
+
# render :xml => exception, :status => 500
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# protected
|
76
|
+
# def deny_access
|
77
|
+
# ...
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# def show_errors(exception)
|
81
|
+
# exception.record.new_record? ? ...
|
82
|
+
# end
|
83
|
+
# end
|
84
|
+
def rescue_from(*klasses, &block)
|
85
|
+
options = klasses.extract_options!
|
86
|
+
unless options.has_key?(:with)
|
87
|
+
block_given? ? options[:with] = block : raise(ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument.")
|
88
|
+
end
|
89
|
+
|
90
|
+
klasses.each do |klass|
|
91
|
+
key = if klass.is_a?(Class) && klass <= Exception
|
92
|
+
klass.name
|
93
|
+
elsif klass.is_a?(String)
|
94
|
+
klass
|
95
|
+
else
|
96
|
+
raise(ArgumentError, "#{klass} is neither an Exception nor a String")
|
97
|
+
end
|
98
|
+
|
99
|
+
# Order is important, we put the pair at the end. When dealing with an
|
100
|
+
# exception we will follow the documented order going from right to left.
|
101
|
+
rescue_handlers << [key, options[:with]]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
protected
|
107
|
+
# Exception handler called when the performance of an action raises an exception.
|
108
|
+
def rescue_the_action(exception)
|
109
|
+
log_error(exception) if logger
|
110
|
+
erase_results if performed?
|
111
|
+
|
112
|
+
# Let the exception alter the response if it wants.
|
113
|
+
# For example, MethodNotAllowed sets the Allow header.
|
114
|
+
if exception.respond_to?(:handle_response!)
|
115
|
+
exception.handle_response!(response)
|
116
|
+
end
|
117
|
+
|
118
|
+
rescue_action(exception)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Overwrite to implement custom logging of errors. By default logs as fatal.
|
122
|
+
def log_error(exception) #:doc:
|
123
|
+
ActiveSupport::Deprecation.silence do
|
124
|
+
if exception.respond_to? :template_error
|
125
|
+
logger.fatal(exception.to_s)
|
126
|
+
else
|
127
|
+
logger.fatal(
|
128
|
+
"\n\n#{exception.class} (#{exception.message}):\n " +
|
129
|
+
clean_backtrace(exception).join("\n ") +
|
130
|
+
"\n\n"
|
131
|
+
)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Overwrite to implement exception handling. By default the exception will be re-raised.
|
137
|
+
# Override this method to provide more user friendly error messages.
|
138
|
+
def rescue_action(exception) #:doc:
|
139
|
+
raise exception
|
140
|
+
end
|
141
|
+
|
142
|
+
# Render detailed diagnostics for unhandled exceptions rescued from
|
143
|
+
# a controller action.
|
144
|
+
# def rescue_action_locally(exception)
|
145
|
+
# @template.instance_variable_set("@exception", exception)
|
146
|
+
# @template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub")))
|
147
|
+
# @template.instance_variable_set("@contents", @template.render(:file => template_path_for_local_rescue(exception)))
|
148
|
+
#
|
149
|
+
# response.content_type = Mime::HTML
|
150
|
+
# render_for_file(rescues_path("layout"), response_code_for_rescue(exception))
|
151
|
+
# end
|
152
|
+
|
153
|
+
# Tries to rescue the exception by looking up and calling a registered handler.
|
154
|
+
def rescue_action_with_handler(exception)
|
155
|
+
if handler = handler_for_rescue(exception)
|
156
|
+
if handler.arity != 0
|
157
|
+
handler.call(exception)
|
158
|
+
else
|
159
|
+
handler.call
|
160
|
+
end
|
161
|
+
true # don't rely on the return value of the handler
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
def perform_action_with_rescue #:nodoc:
|
167
|
+
perform_action_without_rescue
|
168
|
+
rescue Exception => exception
|
169
|
+
rescue_action_with_handler(exception) || rescue_action(exception)
|
170
|
+
end
|
171
|
+
|
172
|
+
def handler_for_rescue(exception)
|
173
|
+
# We go from right to left because pairs are pushed onto rescue_handlers
|
174
|
+
# as rescue_from declarations are found.
|
175
|
+
_, handler = *rescue_handlers.reverse.detect do |klass_name, handler|
|
176
|
+
# The purpose of allowing strings in rescue_from is to support the
|
177
|
+
# declaration of handler associations for exception classes whose
|
178
|
+
# definition is yet unknown.
|
179
|
+
#
|
180
|
+
# Since this loop needs the constants it would be inconsistent to
|
181
|
+
# assume they should exist at this point. An early raised exception
|
182
|
+
# could trigger some other handler and the array could include
|
183
|
+
# precisely a string whose corresponding constant has not yet been
|
184
|
+
# seen. This is why we are tolerant to unknown constants.
|
185
|
+
#
|
186
|
+
# Note that this tolerance only matters if the exception was given as
|
187
|
+
# a string, otherwise a NameError will be raised by the interpreter
|
188
|
+
# itself when rescue_from CONSTANT is executed.
|
189
|
+
klass = self.class.const_get(klass_name) rescue nil
|
190
|
+
klass ||= klass_name.constantize rescue nil
|
191
|
+
exception.is_a?(klass) if klass
|
192
|
+
end
|
193
|
+
|
194
|
+
case handler
|
195
|
+
when Symbol
|
196
|
+
method(handler)
|
197
|
+
when Proc
|
198
|
+
handler.bind(self)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def clean_backtrace(exception)
|
203
|
+
if backtrace = exception.backtrace
|
204
|
+
backtrace.map { |line| line.sub Reactive.configuration.root_dir, '' }
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|