Rails 2.0 will ship with a new ActionController class method called:

1
rescue_from Exception, :with => :method_name

…which will rescue all exceptions in actions, thrown when the application runs in production mode with the given method.

As I read that I thought: Why only in production mode? It seems the Rails team designed this tool to rescue exceptions that were not wanted to be thrown, but why?

In our newest application I had the problem that we wanted to be able to display e.g. a login box on several places all over the app.
We created a login box partial which displayed a form that posted the login to the login action at the user controller. But what if the login fails? We ended up with something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def login
  unless request.post?
    flash[:notice] = :login_failed
    redirect_to_last_page
    return
  end
  user = User.authenticate(params[:nickname], params[:password])
  unless user
    flash[:notice] = :login_failed
    redirect_to_last_page
    return
  end
  #doing the login stuff in session...
end

Mhm… two times the exact same unless block just with a different condition? Not very DRY, isn’t it?

Ok, we’re nearly there where this post started. Why not just throw a UserErrors::LoginFailed Error? This would look like this:

1
2
3
4
5
6
def login
  raise UserError::LoginError unless request.post?
  user = User.authenticate(params[:nickname], params[:password])
  raise UserError::LoginError(user) unless user
  #doing the login stuff in session...
end

Quite cool. Now we outsourced the things that should happen on a login failure to this private method in the user controller:

1
2
3
4
5
def login_error(e)
  flash[:notice] = :login_failed
  redirect_to_last_page
  return
end

And at to get this method work when a UserError::LoginFailed method is thrown we added this as private methods to the application controller:

1
2
3
4
5
6
7
8
9
10
alias old_rescue_action rescue_action
def rescue_action(e)
  key = @@rescue_exceptions.keys.find {|el| e.is_a?(el)}
  return old_rescue_action(e) unless key
  send(@@rescue_exceptions[key][:with], e)
end
 
def self.rescue_from_all(e, params)
  @@rescue_exceptions[e] = params
end

This let us now do this:

1
rescue_from_all UserError::LoginFailed, :with => :login_error

at the top of the user controller.

That’s all. If now a UserError::LoginFailed exception is raised in the controller it gets handled by the login_error method. Not quite a big deal but I like it.

If I find some time this week I will put this to a small plugin, so that you can give it a try even easier.

If you have any questions, or want do discuss if this is nifty or just crap, you’re welcome to leave your comment.

Yours,

Thorben
FEtMab-Team