We all need to handle 404 and 500 errors. Rails handle them differently in different environments.
In development, when we request a non existing resource, we will see:
Routing Error No route matches “/dummy_controller/non_existing_action” with {:method=>get}
Similar situation occurs, when requesting non existing action but in an existing controller:
Unknown action No action responded to smthn_weird. Actions: index, load_adverts, login and logout
Even in production when we make a request from 127.0.0.1 (aka localhost), the request is handled as local.
When we develope our software, informations like those mentioned above are useful, however sometimes we would like to do something else. There might be a situation when you would like to:
- redirect all 'missed' requests into a specified controller (withouth showing 404)
- test 404 and 500 behavior
- show extended info even in production mode
- render different error templates, depending on a module (admin, shopping, etc)
- do any other weird kind of stuff
So, let's overwrite Rails default behavior.
Put into config/environment.rb:
# Show 404 errors SHOW_404 = true # Show error template (or render extended Rails info) SHOW_EXTENDED_404 = false # Don't show 500 - instead render 404 SHOW_ONLY_404 = false
We can use those constants to manipulate Rails "error flow":
- SHOW_404 = false - don't show 404 - use default controller and action to handle response
- SHOW_404 = true – render 404 error
- SHOW_EXTENDED_404 = true – show extended 404 info
- SHOW_EXTENDED_404 = false – render default 404 template (in any of environments)
- SHOW_ONLY_404 – don't show 500 errors - handle them like 404
Next, add into config/routes.rb (just before final "end"):
# 404 route map.connect "*anything", :controller => "default_controller", :action => "index" if !SHOW_404
This will redirect into default controller any requests which do not fit anywhere else. It is worth mentioning here, that it is often not the desired behavior and the user can feel lost when suddenly he will see main page instead of 404 error.
When we use namespaces(modules) and we would like to handle errors differently, depending on a module, we should add:
admin.connect "*anything", :controller => "default_controller", :action => "index" if !SHOW_404 admin.connect ':action' , :controller => "module", :action => "index" if SHOW_404
and for each module also:
admin.controller_name 'controller_name', :controller => 'controller_name'
Now overwrite default error handling methods in controllers/application_controller.rb :
def rescue_action_locally(exception) if SHOW_EXTENDED_404 super exception else rescue_action_in_public(exception) end end
Second method:
def rescue_action_in_public(exception) if SHOW_EXTENDED_404 rescue_action_locally exception else case exception when ActiveRecord::RecordNotFound, ActionController::UnknownAction, ActionController::RoutingError render_error 404 else if SHOW_ONLY_404 render_error 404 else render_error 500 end end end end
So, we have error handling, but we still lack two things:
- render_error method
- path to the errors templates.
Copy 404.html and 500.html from public into views/shared/ and change file extension to .erb. Create in views/layouts/ custom error templates and do not forget to yield :). After that - tell Rails about those templates:
PATH_404 = 'shared/404' PATH_500 = 'shared/500' ERROR_LAYOUT = 'layouts/server_errors'
Finally render_error method:
def render_error(error_nr = 404) p404 = PATH_404; p500 = PATH_500 lay404 = ERROR_LAYOUT; lay500 = ERROR_LAYOUT if block_given? feedback = yield p404 = feedback[0] lay404 = feedback[1] p500 = feedback[2] lay500 = feedback[3] end case error_nr when 404 || SHOW_ONLY_404 render :template => p404, :layout => lay404, :status => "404" else render :template => p500, :layout => lay500, :status => "500" end end
Why I use yield? I have not found a way to pass template parameters from our class into base class. Ofcourse it is possible to overwrite whole render_error in all subclasses but it is not DRY. Instead of this, you can do it like this:
def render_error(error_nr = 404) super do [ 'admin/shared/404', false, 'admin/shared/500', false] end end
First argument contains error partial path, second - layout path. When there is no layout path (error template has already embed layout) - just put false.
0 Comments
2 Pingbacks