What is best strategy to handle exceptions & errors in Rails? What is best strategy to handle exceptions & errors in Rails? ruby-on-rails ruby-on-rails

What is best strategy to handle exceptions & errors in Rails?


The fact that you do a lot of manual checking for exceptions suggests that you are just not using them right. In fact, none of your examples is exceptional.

As for the non-existing post - you should expect your API users (eg. a user using your web via browser) to ask for non-existing posts.

Your second example(Post.find(5).comments[0].created_at) is not exceptional either. Some posts just don't have comments and you know it up front. So why should that throw an exception?

The same is the case with the ActiveRecord::RecordInvalid example. There's just no reason to handle this case by means of an exception. That a user enters some invalid data into a form is a pretty usual thing and there is nothing exceptional about it.

Using the exception mechanism for these kinds of situations might be very convenient in some situations, but it's incorrect for the reasons mentioned above.

With that said, it doesn't mean you can't DRY the code which encapsulates these situations. There's a pretty big chance that you can do it at least to some extent since these are pretty common situations.

So, what about the exceptions? Well, the first rule really is: use them as sparsely as possible.

If you really need to use them there are two kinds of exceptions in general (as I see it):

  1. exceptions that don't break the user's general workflow inside your app (imagine an exception inside your profile picture thumbnail generation routine) and you can either hide them from the user or you just notify him about the problem and its consequences when neccessary

  2. exceptions that preclude the user from using the app at all. These are the last resort and should be handled via the 500 internal server error in web applications.

I tend to use the rescue_from method in the ApplicationController only for the latter, since there are more appropriate places for the first kind and the ApplicationController as the topmost of the controller classes seems to be the right place to fall back to in such circumstances (although nowadays some kind of Rack middleware might be even more appropriate place to put such a thing).

-- EDIT --

The constructive part:

As for the first thing, my advice would be to start using find_by_id instead of find, since it it doesn't throw an exception but returns nil if unsuccessful. Your code would look something like this then:

unless @p = Post.find_by_id(params[:id])  flash[:error] = "Can't find Blog Post"end

which is far less chatty.

Another common idiom for DRYing this kind of situations is to use the controller before_filters to set the often used variables (like @p in this case). After that, your controller might look as follows

controller PostsController  before_filter :set_post, :only => [:create, :show, :destroy, :update]  def show      flash[:error] = "Can't find Blog Post" unless @p  end private  def set_post    @p = Post.find_by_id(params[:id])   endend

As for the second situation (non-existing last comment), one obvious solution to this problem is to move the whole thing into a helper:

# This is just your way of finding out the time of the last comment moved into a # helper. I'm not saying it's the best one ;)def last_comment_datetime(post)  comments = post.comments  if comments.empty?    "No comments, yet."  else    "Last comment for this post at: #{comments.last.created_at}"  endend

Then, in your views, you'd just call

<%= last_comment_datetime(post) %>

In this way the edge case (post without any comments) will be handled in it's own place and it won't clutter the view.

I know, none of these suggests any pattern for handling errors in Rails, but maybe with some refactorings such as these you'll find that a great deal of the need for some kind of strategy for exception/error handling just disappears.


Exceptions are for exceptional circumstances. Bad user input is typically not exceptional; if anything, it's quite common. When you do have an exceptional circumstance, you want to give yourself as much information as possible. In my experience, the best way to do that is to religiously improve your exception handling based on debugging experience. When you bump into an exception, the very first thing you should do is write a unit test for it. The second thing you should do is determine if there is more information that can be added to the exception. More information in this case usually takes the form of catching the exception higher up the stack and either handling it or throwing a new, more informative exception that has the benefit of additional context. My personal rule is that I don't like catching exceptions from much more than three levels up the stack. If an exception has to travel any further than that, you need to be catching it earlier.

As for exposing errors in the UI, if/case statements are totally OK as long as you don't nest them too deeply. That's when this kind of code gets hard to maintain. You can abstract this if it becomes a problem.

For instance:

def flash_assert(conditional, message)  return true if conditional  flash[:error] = message  return falseendflash_assert(Post.exists?(params[:post_id]), "Can't find Blog Post") or return