Exceptional Rails Handling


When I am writing a Rails application it starts out simple with a few if @something.save lines that are pretty self explanatory. Then the application grows and starts to become hairy. Take my contrived example with a grain of salt as I can’t show you production code that I am using.

# app/controllers/widgets_controller.rb
class WidgetsController < ApplicationController

  #
  # Other methods go here
  #

  def something
    # Maybe we handle the update in a special way
    widget = Widget.find(params[:id])
    widget.some_method(params[:widget])

    # for the sake of simplicity let's change the owner
    foobar = widget.foobar
    foobar.owner = current_user

    respond_to do |format|
      if widget.save
        if foobar.save
          format.html { redirect_to widgets_path, :notice => 'Huzzah!' }
        else
          widget.some_special_rollback
          format.html { redirect_to widgets_path, :notice => "You're a moron" }
        end
      else
        format.html { redirect_to widgets_path, :notice => "Not even close"}
      end
    end
  end

end

What did we learn here? Terribly contrived examples can prove anything, No! Though I liked how easy it was to come up with it but that is not why I wrote the code above. I have seen applications get to that point and when I see it now I start to shake my fist violently at the computer and wonder just what in the world were they thinking.

After watching [Avdi Grimm][exceptional-ruby] give his excellent opinion on how you can leverage exception handling in Ruby. I really suggest you go and check it out at the end of this post.

Ruby offers a nice way to handle exceptions with the begin and rescue blocks but I really don’t like how begin can start to clutter your code up. As Grimm has stated, “It can be considered code smell”, and as such should be avoided as much as possible

The other feature that Ruby offers in regards to exception handling is that you can define the rescue part at the bottom of the method. This can be extremely advantageous when trying to organize your code. Let me demonstrate a simple example that we see all the time from the rails generator

# app/controllers/widgets_controller.rb
class WidgetsController < ApplicationController

  def update
    @widget = Widget.find(params[:id])
    @widget.update_attributes(params[:widget])
    @widget.save! # Throw exception if save failed

    respond_to do |format|
      format.html { redirect_to widgets_path, :notice => "It worked" }
    end
  rescue
    # Oh no, the save failed...handle all rollback issues here
    respond_to do |format|
      format.html { render :action => :new }
    end
  end
end

Again the example above is really dead simple but it does demonstrate the point that you can keep your error handling code separated from your non-error code. Now I have found times where I created two models and a third failed to be created and I needed to rollback the changes and set a few of parent’s attributes. This is a case where the transaction block can be used.

# app/controllers/invite_controller.rb
def confirm
  @user = User.find_by_token(param[:token])
  if @user
    Widget.transaction do
      wg = WidgetGroup.create!(:name => "#{@user.name} First Group")
      Widget.create!(:name => "My first widget", widget_group_id => wg.id)
      Widget.create!(:name => "this is a special widget", widget_group_id => wg.id, :special => true)
    end
    redirect_to widget_group_path(wg), :notice => 'Awesome'
  else
    redirect_to root_url, :notice => "This is a terrible app"
  end
end

Transactions are handy and can save you a big head ache. In Computer Science we were always told to keep our memory leaks to a minimum and close all unused file descriptors and other various clean up tasks which would lead me to have special cases where I would have to tear down each object in reverse order from where the error happened. A massive pain if the error happened 4 levels deep.

Where as with transactions, would just rollback the changes and I would be a happy puppy.

Below is Avdi Grimm’s presentation on exeption handling in Ruby and what can be done to clean up code and is an all around way to make yourself more educated on Ruby. Take a look.

Resources

  • [Exceptional Ruby][exceptional-ruby] [exceptional-ruby]: http://blip.tv/avdi-grimm/exceptional-ruby-4778405