38

In Rails 2.3.x, you can override render_optional_error_file like so:

# ApplicationController.rb
protected
  def render_optional_error_file(status_code)
    render :template => "errors/500", :status => 500, :layout => 'application'
  end

However, Rails 3 no longer has render_optional_error_file. Instead, you need to override rescue_action_in_public, which you can do like so:

# config/initializers/error_page.rb
module ActionDispatch
  class ShowExceptions

    protected    
      def rescue_action_in_public(exception)
        status = status_code(exception).to_s

        template = ActionView::Base.new(["#{Rails.root}/app/views"])
        if ["404"].include?(status)
          file = "/errors/404.html.erb"
        else
          file = "/errors/500.html.erb"
        end        
        body = template.render(:file => file)

        render(status, body)
      end

  end
end

This works, but does not use the application's layout. However, if you specify the layout path like so:

body = template.render(:file => file, :layout => "layouts/application") # line 15

You get an Error during failsafe response: ActionView::Template::Error.

Line 4 of application.html.erb:4 is:

<%= stylesheet_link_tag "app", "jquery-ui", "jquery.fancybox", :cache => "all" %>

Whatever ActionView normally uses to render templates isn't getting loaded.

The stack trace is:

  /var/lib/gems/1.8/gems/actionpack-3.0.5/lib/action_view/helpers/asset_tag_helper.rb:794:in `join'
  /var/lib/gems/1.8/gems/actionpack-3.0.5/lib/action_view/helpers/asset_tag_helper.rb:794:in `rails_asset_id'
  /var/lib/gems/1.8/gems/actionpack-3.0.5/lib/action_view/helpers/asset_tag_helper.rb:817:in `rewrite_asset_path'
  /var/lib/gems/1.8/gems/actionpack-3.0.5/lib/action_view/helpers/asset_tag_helper.rb:746:in `compute_public_path'
  /var/lib/gems/1.8/gems/actionpack-3.0.5/lib/action_view/helpers/asset_tag_helper.rb:424:in `path_to_stylesheet'
  /var/lib/gems/1.8/gems/actionpack-3.0.5/lib/action_view/helpers/asset_tag_helper.rb:875:in `ensure_stylesheet_sources!'
  /var/lib/gems/1.8/gems/actionpack-3.0.5/lib/action_view/helpers/asset_tag_helper.rb:874:in `each'
  /var/lib/gems/1.8/gems/actionpack-3.0.5/lib/action_view/helpers/asset_tag_helper.rb:874:in `ensure_stylesheet_sources!'
  /var/lib/gems/1.8/gems/actionpack-3.0.5/lib/action_view/helpers/asset_tag_helper.rb:512:in `stylesheet_link_tag'
  /data/sites/fundraisers-stage/releases/20110316194843/app/views/layouts/application.html.erb:4:in `_app_views_layouts_application_html_erb___19482063_70294907435920_0'
  /var/lib/gems/1.8/gems/actionpack-3.0.5/lib/action_view/template.rb:135:in `send'
  /var/lib/gems/1.8/gems/actionpack-3.0.5/lib/action_view/template.rb:135:in `render'
  /var/lib/gems/1.8/gems/activesupport-3.0.5/lib/active_support/notifications.rb:54:in `instrument'
  /var/lib/gems/1.8/gems/actionpack-3.0.5/lib/action_view/template.rb:127:in `render'
  /var/lib/gems/1.8/gems/actionpack-3.0.5/lib/action_view/render/layouts.rb:80:in `_render_layout'
  /var/lib/gems/1.8/gems/actionpack-3.0.5/lib/action_view/render/rendering.rb:62:in `_render_template'
  /var/lib/gems/1.8/gems/activesupport-3.0.5/lib/active_support/notifications.rb:52:in `instrument'
  /var/lib/gems/1.8/gems/activesupport-3.0.5/lib/active_support/notifications/instrumenter.rb:21:in `instrument'
  /var/lib/gems/1.8/gems/activesupport-3.0.5/lib/active_support/notifications.rb:52:in `instrument'
  /var/lib/gems/1.8/gems/actionpack-3.0.5/lib/action_view/render/rendering.rb:56:in `_render_template'
  /var/lib/gems/1.8/gems/actionpack-3.0.5/lib/action_view/render/rendering.rb:26:in `render'
  /data/sites/fundraisers-stage/releases/20110316194843/config/initializers/error_pages.rb:15:in `rescue_action_in_public'
Paul Schreiber
  • 12,094
  • 4
  • 36
  • 61

4 Answers4

55

In rails 3.2, it's easier than that:

Add this to config/application.rb:

config.exceptions_app = self.routes

That causes errors to be routed via the router. Then you just add to config/routes.rb:

match "/404", :to => "errors#not_found"

I got this info from item #3 on the blog post "My five favorite hidden features in Rails 3.2" by By José Valim.

docwhat
  • 10,393
  • 6
  • 53
  • 51
  • 3
    The `config.exceptions_app = self.routes` works for the 404 error, but it also keeps the 500 error from loading the 500.html file. Is it possible to set the 404 to be loaded from the app but keep the 500 error loading from the static file? (Because if something is broken in the app, it might cause the error message to also break.) Thanks! – James Pierce Aug 23 '12 at 23:11
  • In response to my comment, this post might help: http://ariejan.net/2011/10/14/rails-3-customized-exception-handling – James Pierce Aug 23 '12 at 23:31
  • Here's a great post on handling custom exceptions: http://geekmonkey.org/articles/29-exception-applications-in-rails-3-2 – Hamed Apr 16 '13 at 13:20
  • Here's sample implmentation: https://github.com/sheerun/rails4-bootstrap/issues/26 – sheerun Oct 16 '13 at 14:05
  • You should also handle 500 and 422 errors as well: `%w(404 422 500).each { |code| get code, to: "errors#show", code: code }` and in ErrorsController show method: `@status = params[:code] || 500` `render @status` – Jarvis Johnson Mar 08 '18 at 20:11
51

I would suggest using rescue_from instead. You would just rescue from specific errors rather than overriding rescue_action_in_public. This is especially useful when dealing with user-defined errors or controller-specific errors.

# ApplicationController
rescue_from ActionController::RoutingError, :with => :render_404
rescue_from ActionController::UnknownAction, :with => :render_404
rescue_from ActiveRecord::RecordNotFound, :with => :render_404
rescue_from MyApp::CustomError, :with => :custom_error_resolution

def render_404
  if /(jpe?g|png|gif)/i === request.path
    render :text => "404 Not Found", :status => 404
  else
    render :template => "shared/404", :layout => 'application', :status => 404
  end
end

# UsersController
rescue_from MyApp::SomeReallySpecificUserError, :with => :user_controller_resolution

http://api.rubyonrails.org/classes/ActiveSupport/Rescuable/ClassMethods.html

mnelson
  • 2,872
  • 1
  • 15
  • 19
  • 1
    The problem with using `rescue_from` is that it halts the error chain. I don't want the error chain to be halted; I want it to continue to propagate. That way, `exception_notifier` will still work. – Paul Schreiber Mar 19 '11 at 14:55
  • 1
    Gotcha; I use hoptoad so I usually end up doing a 'rescue_from Exception' as a catch-all, notify hoptoad with the error, then execute some dynamic error handling. Can you try... render(:template => file, :layout => '/layouts/application', :status => 404) and see if that works? – mnelson Mar 19 '11 at 15:32
  • That makes no difference — same backtrace as before. I need to find some way to have ActionView's context set up as it expects. Perhaps I can call its render function directly? – Paul Schreiber Mar 19 '11 at 16:03
  • Does it make a difference if you make your code a module and include it in ApplicationController? – mnelson Mar 19 '11 at 18:10
  • How would I do that? It's currently a module: `module ActionDispatch class ShowExceptions`. – Paul Schreiber Mar 20 '11 at 03:19
  • I tried that, and 500 errors give me an empty response (`Content-Length: 0`). – Paul Schreiber Mar 25 '11 at 20:47
3

The exception notifier has a method called notify_about_exception to initiate the error notification on demand.

class ApplicationController < ActionController::Base
  include ExceptionNotification::Notifiable

  rescue_from Exception, :with => :render_all_errors

  def render_all_errors(e)
    log_error(e) # log the error
    notify_about_exception(e) # send the error notification

    # now handle the page
    if e.is_a?(ActionController::RoutingError)
      render_404(e)
    else
      render_other_error(e)
    end
  end

  def render_404_error(e)
   # your code
  end

  def render_other_error(e)
   # your code
  end
end
Harish Shetty
  • 62,259
  • 19
  • 144
  • 196
  • 1
    This is useful, but not general-purpose — it only works with a specific ExceptionNotifier, and fails if one's not installed. I'm using https://github.com/railsware/exception_notification. I suppose you could test for `defined?()`, but really, I just want to override the rendering code, not the whole stack. – Paul Schreiber Mar 20 '11 at 21:58
0

I have also faced such problem. The problem is because of attachment_fu gem or plugin. Just uninstall it and use any other plugin or gem will solve your problem.

Neeraj Kumar
  • 3,283
  • 23
  • 19