Skip to content

Commit

Permalink
Inject errors during rendering into the foot of the document.
Browse files Browse the repository at this point in the history
  • Loading branch information
oggy committed Sep 23, 2010
1 parent b7d87bc commit 982b20d
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 12 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG
Expand Up @@ -2,7 +2,7 @@


* Replace prelayouts with :progressive => true option to #layout. * Replace prelayouts with :progressive => true option to #layout.
* Add render :progressive => true option for nested layouts. * 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. * New Relic support.


== 0.0.11 2010-05-10 == 0.0.11 2010-05-10
Expand Down
24 changes: 13 additions & 11 deletions lib/template_streaming.rb
Expand Up @@ -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? elsif options[:layout].is_a?(ActionView::Template) && controller.class.render_progressively?
# Toplevel render call, from the controller. # Toplevel render call, from the controller.
layout = options.delete(:layout) 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)) render(options.merge(:file => layout.path_without_format_and_extension))
end end
elsif options[:progressive] elsif options[:progressive]
layout = options.delete(:layout) layout = options.delete(:layout)
with_proc_for_layout( lambda{render(options)} ) do |*args| with_render_proc_for_layout(options) do
if args.empty? if (options[:inline] || options[:file] || options[:text])
if (options[:inline] || options[:file] || options[:text]) render(:file => layout, :locals => local_assigns)
render(:file => layout, :locals => local_assigns)
else
render(options.merge(:partial => layout))
end
else else
instance_variable_get(:"@content_for_#{args.first}") render(options.merge(:partial => layout))
end end
end end
else else
_render_with_layout_without_template_streaming(options, local_assigns, &block) _render_with_layout_without_template_streaming(options, local_assigns, &block)
end end
end end


def with_proc_for_layout(proc) def with_render_proc_for_layout(options)
original_proc_for_layout = @_proc_for_layout 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 begin
# TODO: what is @cached_content_for_layout in base.rb ? # TODO: what is @cached_content_for_layout in base.rb ?
yield yield
Expand Down
107 changes: 107 additions & 0 deletions lib/template_streaming/error_recovery.rb
@@ -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
37 changes: 37 additions & 0 deletions lib/template_streaming/templates/errors.erb
@@ -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>
1 change: 1 addition & 0 deletions rails/init.rb
@@ -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)

0 comments on commit 982b20d

Please sign in to comment.