Permalink
Browse files

Inject errors during rendering into the foot of the document.

  • Loading branch information...
1 parent b7d87bc commit 982b20d402000e32e0115d9c2f50bfc319c96e0e @oggy committed Sep 23, 2010
Showing with 159 additions and 12 deletions.
  1. +1 −1 CHANGELOG
  2. +13 −11 lib/template_streaming.rb
  3. +107 −0 lib/template_streaming/error_recovery.rb
  4. +37 −0 lib/template_streaming/templates/errors.erb
  5. +1 −0 rails/init.rb
View
@@ -2,7 +2,7 @@
* Replace prelayouts with :progressive => true option to #layout.
* Add render :progressive => true option for nested layouts.
- * Add initializer (fixes use as a plugin).
+ * Inject errors during rendering to end of body.
* New Relic support.
== 0.0.11 2010-05-10
View
@@ -233,30 +233,32 @@ def _render_with_layout_with_template_streaming(options, local_assigns, &block)
elsif options[:layout].is_a?(ActionView::Template) && controller.class.render_progressively?
# Toplevel render call, from the controller.
layout = options.delete(:layout)
- with_proc_for_layout( lambda{render(options)} ) do
+ with_render_proc_for_layout(options) do
render(options.merge(:file => layout.path_without_format_and_extension))
end
elsif options[:progressive]
layout = options.delete(:layout)
- with_proc_for_layout( lambda{render(options)} ) do |*args|
- if args.empty?
- if (options[:inline] || options[:file] || options[:text])
- render(:file => layout, :locals => local_assigns)
- else
- render(options.merge(:partial => layout))
- end
+ with_render_proc_for_layout(options) do
+ if (options[:inline] || options[:file] || options[:text])
+ render(:file => layout, :locals => local_assigns)
else
- instance_variable_get(:"@content_for_#{args.first}")
+ render(options.merge(:partial => layout))
end
end
else
_render_with_layout_without_template_streaming(options, local_assigns, &block)
end
end
- def with_proc_for_layout(proc)
+ def with_render_proc_for_layout(options)
original_proc_for_layout = @_proc_for_layout
- @_proc_for_layout = proc
+ @_proc_for_layout = lambda do |*args|
+ if args.empty?
+ render(options)
+ else
+ instance_variable_get(:"@content_for_#{args.first}")
+ end
+ end
begin
# TODO: what is @cached_content_for_layout in base.rb ?
yield
@@ -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
@@ -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>
View
@@ -1,2 +1,3 @@
require 'template_streaming'
+require 'template_streaming/error_recovery'
require 'template_streaming/new_relic' if defined?(NewRelic)

0 comments on commit 982b20d

Please sign in to comment.