raise and the exception object

Free preview 3 min to read

A crash you've already seen

In an earlier course, code you wrote raised ActiveRecord::RecordNotFound or ActiveModel::ForbiddenAttributesError. The page broke, a stack trace showed up, and the fix was to change the code so the error stopped happening. This course is about what happens in that moment, and how to take control of it instead of only avoiding it.

def open_account(balance)
  raise "balance cannot be negative" if balance < 0
  balance
end

open_account(-50)

Running this crashes the program:

RuntimeError: balance cannot be negative

raise is a Ruby keyword; Rails didn't invent it. Called with a string, it builds a RuntimeError with that string as its message and stops normal execution at once. The exception unwinds up through every method call until something catches it or the program dies.

An exception is an object

RuntimeError, ArgumentError, ZeroDivisionError and ActiveRecord::RecordNotFound are all classes, and every raised exception is an instance of one, like any other Ruby object.

begin
  1 / 0
rescue => e
  e.class
  # => ZeroDivisionError
  e.message
  # => "divided by 0"
end

e.class and e.message work because e is an object in memory with its own state, not a shapeless crash signal. Every exception class in Ruby eventually inherits from Exception, the root of the hierarchy. raise can also take a class and a message:

raise ArgumentError, "amount must be a number"

That builds an ArgumentError with that message and raises it. It's the same mechanism as before, naming the class instead of falling back to the default RuntimeError.

Why this is worth a whole course

An unhandled exception does more than print an ugly message. It stops the method it happened in, then the method that called it, then the next one up, until some code along the way says "I'll handle this" or the program halts. A RecordNotFound that nothing catches unwinds up to the Rails request. Then the framework has to step in and turn it into a 500 or, with the right handling, a friendly "not found" page. The rest of this course is about writing that "I'll handle this" step on purpose. It covers begin/rescue/else/ensure, the difference between catching one specific problem and catching almost anything, and your own exception classes with messages that explain what went wrong.

What this course covers

  • begin/rescue/else/ensure/end, the full structure for catching a raised exception and reacting to it.
  • Rescuing one specific exception class versus a bare rescue, and the line between what a bare rescue catches and what it leaves alone on purpose.
  • ensure, and showing that it runs whatever happens in the block above it.
  • Writing a custom exception class and raising it with a message that means something.
  • ActiveRecord::RecordNotFound, ActiveModel::ForbiddenAttributesError, ActionController::ParameterMissing and ActiveRecord::RecordInvalid, exceptions from earlier courses, as targets for rescue SomeSpecificError => e.

Key takeaways

  • raise stops normal execution and unwinds up through every calling method until something catches the exception or the program halts.
  • An exception is an object, an instance of a class like RuntimeError or ZeroDivisionError, with its own .class and .message.
  • raise "text" builds a RuntimeError; raise SomeClass, "text" builds an instance of SomeClass with that message.
  • This course is about catching that unwinding on purpose instead of letting it reach the top and crash the program.
Lesson 1 of 7 · free

Keep going with lesson 2

begin/rescue: catching a specific exception

Using begin and rescue to catch one exception class, ordering several rescue clauses, and rescuing a whole method body.

A free account also gives you the quizzes, your progress and notes, and the discussion.

Type to start searching...