Rails 3: Customized exception handling
14 Oct, 2011

Exceptions happen. There’s no way around that. But not all exceptions are created equally.

Let me give you an example of how to handle a ActiveRecord::RecordNotFound exception. Let’s assume you have an application that could show a user profile:

# GET /p/:name
def show
@profile = Profile.find(params[:name])
end

Now, it may happen that the :name paramater contains a value that cannot be found in our database, most likely because someone made a typo in the URL.

If Profile#find cannot get a proper result it will throw ActiveRecord::RecordNotFound.

Now, instead of showing the user the (by default ugly) 404 page from public/404.html we want to do something more fancy.

## Action-specific exception handling

Here’s one solution:

# GET /p/:name
def show
@profile = Profile.find(params[:name])
rescue
render :template => 'application/profile_not_found', :status => :not_found
end

You can now create app/views/applicaiton/profile_not_found.html.haml and give a nice custom error message to your user.

You may try to find some matching profiles to :name or show a search box.

## Global exception handling

The above example only works for the specific profile show action. It’s also possible to hanlde exceptions on the application level.

Your show action still looks like this:

# GET /p/:name
def show
@profile = Profile.find(params[:name])
end

Then, in your app/controllers/application_controller.rb add this:

class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, :with => :rescue_not_found

protected
def rescue_not_found
render :template => 'application/not_found', :status => :not_found
end
end

Whenever an ActiveRecord::RecordNotFound exception is thrown (and not handled by the action itself), it will be handled by your ApplicationController.

## Custom exceptions

It’s possible to throw your own custom exceptions and handle them in different ways. Like this:

# Define your own error
class MyApp::ProfileNotFoundError < StandardError
end

# GET /p/:name
def show
@profile = Profile.find_by_name(params[:name])
raise MyApp::ProfileNotFoundError if @profile.nil?
end

And add this to your ApplicationController:

rescue_from MyApp::ProfileNotFoundError, :with => :profile_not_found

Optionally, if you don’t want to write that custom profile_not_found method, you may also supply a block:

rescue_from MyApp::ProfileNotFoundError do |exception|
end