Never Raise StandardError Directly

Mon, 20 Nov 2006 22:40:00 -0800
ruby, rails, practice

It's not a good idea to raise StandardError directly from your Rails application, particularly when using an exception as a way to indicate a recoverable error from an ActiveRecord transaction. In such cases, it's better to subclass from StandardError and raise the subclass instead. If you use StandardError directly, you may find that your system is catching and recovering from errors that are related to faulty logic rather than broken business rules.

For example, consider the following bad code:

1
2
3
4
5
6
7
8
9
10
11




def some_method(x)
  x[x.length] + 1
  raise StandardError if x[0] < 2
end

begin
  a = [1, 2]
  some_method( a )
rescue StandardError
  puts "Bad value for index 0: #{a[0]}"
end

This code does nothing useful, but assuming that it did, and assuming that you're catching StandardError for the case when the first index of the given array has an invalid value, you're in for a surprise. In fact, the error that would be caught by the rescue clause above is thrown by the code on line 2. In fact, the actual error thrown by this line is an instance of NoMethodError, which is a subclass of NameError, which is a subclass of StandardError.

If you wrote a unit test for the some_method method, and asserted that StandardError was raised, it would pass incorrectly. This is especially misleading if you are writing a test for a controller action and asserting that, upon some error condition, the application redirects the user to a page displaying that error. I find that I don't explicitly raise many errors from my application code, except when I wrap the code in a controller action inside an ActiveRecord transaction. The only way to roll back the transaction is to raise an exception from within the transaction and rescue from it outside, such as:

class SomeController < ApplicationController
  def some_action
    begin
      SomeRecordClass.transaction do
        a = [1, 2]
        some_method( a )
      end
    rescue StandardError
      @error_message = "Bad value for index 0: #{a[0]}"
      render 'some_template'
      return
    end
    redirect_to some_url
  end
end

In the above code, you can't avoid raising an exception if you want to roll back the transaction. In this case, rather than rescuing from StandardError, rescue from an application-specific exception that inherits from StandardError. This way you know that you're recovering from an application error, and not faulty logic in the code.