Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Inject errors during rendering into the foot of the document.
- Loading branch information
Showing
5 changed files
with
159 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,107 @@ | |||
module TemplateStreaming | |||
class << self | |||
# | |||
# Call the given block when an error occurs during rendering. | |||
# | |||
# The block is called with the exception object. | |||
# | |||
# This is where you should hook in your exception notification | |||
# system of choice (Hoptoad, Exceptional, etc.) | |||
# | |||
def on_render_error(&block) | |||
ErrorRecovery.callbacks << block | |||
end | |||
end | |||
|
|||
module ErrorRecovery | |||
class << self | |||
attr_accessor :callbacks | |||
end | |||
self.callbacks = [] | |||
|
|||
EXCEPTIONS_KEY = 'template_streaming.exceptions'.freeze | |||
CONTROLLER_KEY = 'template_streaming.template'.freeze | |||
|
|||
class Middleware | |||
def initialize(app) | |||
@app = app | |||
end | |||
|
|||
def call(env) | |||
@env = env | |||
env[EXCEPTIONS_KEY] = [] | |||
status, headers, @body = *@app.call(env) | |||
[status, headers, self] | |||
end | |||
|
|||
def each(&block) | |||
controller = @env[CONTROLLER_KEY] | |||
if controller && controller.send(:local_request?) | |||
exceptions = @env[EXCEPTIONS_KEY] | |||
template = controller.response.template | |||
@body.each do |chunk| | |||
if !exceptions.empty? && (insertion_point = chunk =~ %r'</body\s*>\s*(?:</html\s*>\s*)?\z'im) | |||
chunk.insert(insertion_point, template.render_exceptions(exceptions)) | |||
exceptions.clear | |||
end | |||
yield chunk | |||
end | |||
if !exceptions.empty? | |||
yield template.render_exceptions(exceptions) | |||
end | |||
else | |||
@body.each(&block) | |||
end | |||
end | |||
end | |||
|
|||
module Controller | |||
def self.included(base) | |||
base.when_streaming_template :recover_from_errors | |||
base.helper Helper | |||
base.helper_method :recover_from_errors? | |||
end | |||
|
|||
def recover_from_errors | |||
@recover_from_errors = true | |||
request.env[CONTROLLER_KEY] = self | |||
end | |||
|
|||
def recover_from_errors? | |||
@recover_from_errors | |||
end | |||
end | |||
|
|||
module Helper | |||
def render_partial(*) | |||
begin | |||
super | |||
rescue ActionView::MissingTemplate => e | |||
# ActionView uses this as a signal to try another template engine. | |||
raise e | |||
rescue Exception => e | |||
raise e if !recover_from_errors? | |||
Rails.logger.error("#{e.class}: #{e.message}") | |||
Rails.logger.error(e.backtrace.join("\n").gsub(/^/, ' ')) | |||
callbacks = ErrorRecovery.callbacks and | |||
callbacks.each{|c| c.call(e)} | |||
request.env[EXCEPTIONS_KEY] << e | |||
'' | |||
end | |||
end | |||
|
|||
def render_exceptions(exceptions) | |||
@content = exceptions.map do |exception| | |||
template_path = ActionController::Rescue::RESCUES_TEMPLATE_PATH | |||
@exception = exception | |||
@rescues_path = template_path | |||
render :file => "#{template_path}/rescues/template_error.erb" | |||
end.join | |||
render :file => "#{File.dirname(__FILE__)}/templates/errors.erb" | |||
end | |||
end | |||
|
|||
ActionController::Base.send :include, Controller | |||
ActionController::Dispatcher.middleware.insert_after ActionController::Failsafe, Middleware | |||
end | |||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,37 @@ | |||
<style> | |||
#uncaught_exceptions { | |||
position: absolute; | |||
top: 0px; | |||
left: 0px; | |||
margin: 8px; | |||
background-color: #fff; color: #333; | |||
} | |||
|
|||
#uncaught_exceptions, | |||
#uncaught_exceptions p, | |||
#uncaught_exceptions ol, | |||
#uncaught_exceptions ul, | |||
#uncaught_exceptions td { | |||
font-family: verdana, arial, helvetica, sans-serif; | |||
font-size: 13px; | |||
line-height: 18px; | |||
} | |||
|
|||
#uncaught_exceptions h1 { | |||
margin-top: 0px; | |||
} | |||
|
|||
#uncaught_exceptions pre { | |||
background-color: #eee; | |||
padding: 10px; | |||
font-size: 11px; | |||
} | |||
|
|||
#uncaught_exceptions a { color: #000; } | |||
#uncaught_exceptions a:visited { color: #666; } | |||
#uncaught_exceptions a:hover { color: #fff; background-color:#000; } | |||
</style> | |||
|
|||
<div id="uncaught_exceptions"> | |||
<%= @content %> | |||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -1,2 +1,3 @@ | |||
require 'template_streaming' | require 'template_streaming' | ||
require 'template_streaming/error_recovery' | |||
require 'template_streaming/new_relic' if defined?(NewRelic) | require 'template_streaming/new_relic' if defined?(NewRelic) |