reactive-mvc 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|