In Rails 1.1.6, it responded with a HTTP/1.x 404 Not Found error, displaying the contents of public/404.html if it existed.
And everyone was happy. Because that made sense.
The new 1.2 behavior is to raise a nasty RoutingError (which then gets e-mailed to you along with all the other exceptions, if you're using ExceptionNotifier) and then show the user a nasty 500 error page.
This is not the desired behavior for me, for a couple reasons:
Apparently I'm not the only one to think this new behavior sucks...
http://brian.pontarelli.com/2007/01/14/handling-rails-404-and-500-errors/.
I spent a couple of hours trying to figure out how to handle 404 and 500 errors in Rails. This is not simple and actually really annoying. Hopefully future versions clean this up because right now it sucks pretty badly
http://forum.textdrive.com/viewtopic.php?id=16694 suggests doing something like this in ApplicationController:
def rescue_action_in_public(exception)
case exception
when ActiveRecord::RecordNotFound, ActionController::RoutingError, ActionController::UnknownController, ActionController::UnknownAction
#render_404
else
#render_500
end
end
However, if you are using the exception_notification plugin (and you should be!) (> r7360), then this is handled for you:
./lib/exception_notifiable.rb
def exceptions_to_treat_as_404
exceptions = [ActiveRecord::RecordNotFound,
ActionController::UnknownController,
ActionController::UnknownAction]
exceptions << ActionController::RoutingError if ActionController.const_defined?(:RoutingError)
exceptions
end
def rescue_action_in_public(exception)
case exception
when *self.class.exceptions_to_treat_as_404
render_404
else
render_500
...
ExceptionNotifier.deliver_exception_notification(exception, self,
request, data)
end
end
So install exception_notification and be happy again!
Other good links:
Just look at the source for rescue_action(exception):
# File vendor/rails/actionpack/lib/action_controller/rescue.rb, line 26
def rescue_action(exception)
log_error(exception) if logger
erase_results if performed?
if consider_all_requests_local || local_request?
rescue_action_locally(exception)
else
rescue_action_in_public(exception)
end
end
It
Rails makes the following assumptions:
To decide whether you are a developer or an "everyone else", it doesn't look at your environment ('development' or 'production'), like I might have expected, but at your IP address (by default, 127.0.0.1 is the only IP considered local by default -- which basically only includes the case when you are doing development with webrick). That could be useful, I suppose, if you were debugging an error remotely and wanted it to automatically show the developer view any time you connected from your home computer. But I'm still not sure I like it.... My opinion:
See http://api.rubyonrails.org/classes/ActionController/Rescue.html .
I might find it useful to override local_request?() sort of like this:
def local_request?() if environment == "production" then false if environment == "development" then true end
I just noticed that something like this is already possible via the environments/* files:
config/environments/development.rb:
# Show full error reports config.action_controller.consider_all_requests_local = true
That will ensure that all requests done during development will have a local rescue (verbose errors).
(Question: Won't that happen anyway? When would the request be not from localhost?)
(Complaint: Why don't they name it for what it does rather than how it works? It should be called config.action_controller.show_full_error_reports, not config.action_controller.consider_all_requests_local! "local" doesn't mean much to me -- at least not what they think it means.)
Well, it depends what you want to debug... The default behavior is to show a different error page (rescue_action_locally) during development, one that gives the full error details and a backtrace.
That's usually fine... unless perhaps you're testing the error handling itself and you want it to be identical to production.
In that case, this might be useful... (put this in app/controllers/application.rb)
def rescue_action_locally(exception)
rescue_action_in_public(exception)
super
end
For instance, right after you install exception_notification in an app, you may want to test that it actually works in development before sending it out into the wild (where it could potentially break horribly)...
Question: will that actually cause it to try to send exception e-mails to someone for every exception that is raised during development?
"Application error" is found in public/500.html and (I believe) is rendered by ... (??)
"Application error (Rails)", on the other hand, is defined here:
/usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_controller/rescue.rb:
# Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>).
def rescue_action_in_public(exception) #:doc:
case exception
when RoutingError, UnknownAction then
render_text(IO.read(File.join(RAILS_ROOT, 'public', '404.html')), "404 Not Found")
else render_text "<html><body><h1>Application error (Rails)</h1></body></html>"
end
end
It is also defined here, but this probably doesn't happen as often:
/usr/lib/ruby/gems/1.8/gems/rails-1.1.6/lib/dispatcher.rb
# If the block raises, send status code as a last-ditch response.
def failsafe_response(output, status, exception = nil)
yield
rescue Object
begin
output.write "Status: #{status}\r\n"
if exception
message = exception.to_s + "\r\n" + exception.backtrace.join("\r\n")
error_path = File.join(RAILS_ROOT, 'public', '500.html')
if defined?(RAILS_DEFAULT_LOGGER) && !RAILS_DEFAULT_LOGGER.nil?
RAILS_DEFAULT_LOGGER.fatal(message)
output.write "Content-Type: text/html\r\n\r\n"
if File.exists?(error_path)
output.write(IO.read(error_path))
else
output.write("<html><body><h1>Application error (Rails)</h1></body></html>")
end
else
output.write "Content-Type: text/plain\r\n\r\n"
output.write(message)
end
end
rescue Object
end
end
When would that get called instead of the normal 500 error? I don't know yet...
Normally, it looks like the ugly "Application error (Rails)" message is rendered for all "normal" exceptions (rescue_action_in_public in /usr/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_controller/rescue.rb). This is surprising; I would have expected it to use 500.rhtml. (Why doesn't it??)
However, if you have the exception_notifiable plugin installed (which I recommend everyone do!), then rescue_action_in_public will be overridden to have the desired behavior. Yay!
./vendor/plugins/exception_notification/lib/exception_notifiable.rb:
def render_500
respond_to do |type|
type.html { render :file => "#{RAILS_ROOT}/public/500.html", :status => "500 Error" }
type.all { render :nothing => true, :status => "500 Error" }
end
end
def rescue_action_in_public(exception)
case exception
when ActiveRecord::RecordNotFound, ActionController::UnknownController, ActionController::UnknownAction
render_404
else
render_500
deliverer = self.class.exception_data
data = case deliverer
when nil then {}
when Symbol then send(deliverer)
when Proc then deliverer.call(self)
end
ExceptionNotifier.deliver_exception_notification(exception, self,
request, data)
end
end
http://wiki.rubyonrails.com/rails/pages/HowtoConfigureTheErrorPageForYourRailsApp