Code Along: Custom Error Handling

Objectives

  • Create and raise custom error messages in Ruby.

Introduction

Ruby has a hierarchy of error, or Exception, classes, all of which inherit from the Exception class. You'll become familiar with these error types:

  • NoMethodError
  • ArgumentError
  • SyntaxError

And these are just a few! Let's say, however, that we are working on a web application in which users can sign in and post pictures to Instagram. But wait! Instagram has been hacked and their entire site is currently down! Since our app relies on sending data to and getting a response from the Instagram site, our app will break and our users won't know why. They'll only know that our app is broken and they may even stop using it entirely. Not good. Lucky for us, we can use custom error messages and custom error handling to save the day!

By defining custom error messages and handling, we can show our users a specific error message in the event of a disaster like the one above. By handling these custom errors in a particular way, we can soothe our users by redirecting them somewhere useful, showing them some kind of clear and apologetic notice, or showing them a fun, relaxing picture of a cat.

We'll learn more about these common use cases for handling errors in web applications later on in this course. In this reading and the following lab, we'll practice building simple custom errors.

This is a code along. There are no tests to pass, but you must manually fork and clone the repo in order to code along!

Before we begin, a note on inheritance

If one class inherits from another, that means it takes on all of the methods and behaviors of the class from which it inherits. In the below example, the Child class inherits from the Parent class. Consequently, all instances of Child have not only the behaviors and methods defined directly in the Child class itself but also all of the methods and behaviors defined in the Parent class:

class Child < Parent
end

Building a custom error

To build a custom error, we define an error class that inherits from the Exception class. Which class your custom error inherits from will likely depend on the situation in which you want to raise it. However, it is usually a pretty safe bet to inherit your custom error class from the StandardError class. For more info on error class hierarchies, you can review this chart of error class inheritance:

Exception
 NoMemoryError
 ScriptError
   LoadError
   NotImplementedError
   SyntaxError
 SignalException
   Interrupt
 StandardError
   ArgumentError
   IOError
     EOFError
   IndexError
   LocalJumpError
   NameError
     NoMethodError
   RangeError
     FloatDomainError
   RegexpError
   RuntimeError
   SecurityError
   SystemCallError
   SystemStackError
   ThreadError
   TypeError
   ZeroDivisionError
 SystemExit
 fatal

Defining our error class

Let's look at the example of our Person class and its #get_married method. In custom_errors.rb, we have the following code:

class Person
  attr_accessor :name, :partner
  
  def initialize(name)
    @name = name
  end
  
  def get_married(person)
    self.partner = person
    person.partner = self
  end
end

beyonce = Person.new("Beyonce")
beyonce.get_married("Jay-Z")
puts beyonce.name

As it currently stands, we would receive a NoMethodError if we try to pass #get_married an argument of anything that is not an instance of the Person class.

For example, at the bottom of our custom_errors.rb file, we're trying to tell Beyonce to #get_married to "Jay-Z". The problem is that "Jay-Z" is a string, not an instance of the Person class.

Run the code in the custom_errors.rb file with the ruby custom_errors.rb command. You should see the following output:

custom_errors.rb:10:in `get_married': undefined method `partner=' for "Jay-Z":String (NoMethodError)

That's pretty informative as errors go. However, we're here to learn about raising our very own custom errors. So, for the sake of this example, let's say we are not satisfied with this error. Let's make our own!

Step 1: Defining the custom error class

Let's define a custom error class, PartnerError that inherits from StandardError:

class PartnerError < StandardError
end

Okay, we have the code for our custom error class right here, but where does it belong in our application? We have a couple of options. We can simply place the above code inside of the Person class. We could define it outside of our Person class. Or, we can create a module and include that module inside the Person class. For now, we're going to include our custom error class inside of our Person class:

class Person
  ...

  def get_married(person)
    self.partner = person
    person.partner = self
  end

  # Add the following two lines:
  class PartnerError < StandardError
  end
end

beyonce = Person.new("Beyonce")
beyonce.get_married("Jay-Z")
puts beyonce.name

Now we're ready to use our custom error inside our #get_married method.

Step 2: Raising our custom error

We need to tell our program to raise our brand new PartnerError when the argument passed into the #get_married method is not an instance of the Person class. We can do that with the raise keyword. Place the following code in your #get_married method:

class Person
  attr_accessor :partner, :name

  def initialize(name)
    @name = name
  end

  def get_married(person)
    self.partner = person
    if person.class != Person
      raise PartnerError
    else
      person.partner = self
    end
  end

  class PartnerError < StandardError
  end
end

beyonce = Person.new("Beyonce")
beyonce.get_married("Jay-Z")
puts beyonce.name

Now, go ahead and run the file again. This time you should see the following in your terminal:

custom_errors.rb:11:in `get_married': Person::PartnerError (Person::PartnerError)

We did it! We raised our very own custom error. However, our program is still broken. Notice that the puts beyonce.name line at the bottom of our file won't run because it follows the #get_married method call, and we called that method in such a way as to raise an error. If only there was a way for us to rescue our program when such an error is raised and allow it to keep running...

Custom error handling

We can achieve the above goal via something called rescuing. Before we look at how to rescue the errors we raise and allow our program to continue to run, let's think about the desired behavior of our rescue.

Step 1: Writing a custom error message

Of course we want our program to continue running after we raise the error. It would also be nice to output a custom error message when the error is raised. Let's add a message to our PartnerError class:

class PartnerError < StandardError
  def message
    "you must give the get_married method an argument of an instance of the person class!"
  end
end

Now we have a nice, informative, custom error message that will make it really clear to our users what went wrong if they encounter this error. Now we're ready to implement our rescue.

Step 2: Implementing the rescue

The basic pattern of error rescuing is as follows:

begin
  raise YourCustomError
rescue YourCustomError
end

Let's implement this code in our #get_married method:

def get_married(person)
    self.partner = person
    if person.class != Person
      begin
        raise PartnerError
      rescue PartnerError => error
          puts error.message
      end
    else
      person.partner = self
    end
end

If the object passed into the method as an argument is not an instance of the Person class, we will begin our error handling. First, we raise our PartnerError, then we rescue our PartnerError. The rescue method creates an instance of the PartnerError class and puts out the result of calling message on that instance.

At this point, the code in custom_errors.rb should look like this:

class Person
  attr_accessor :partner, :name

  def initialize(name)
    @name = name
  end

  def get_married(person)
    self.partner = person
    if person.class != Person
      begin
        raise PartnerError
      rescue PartnerError => error
          puts error.message
      end
    else
      person.partner = self
    end
  end

  class PartnerError < StandardError
    def message
      "you must give the get_married method an argument of an instance of the person class!"
    end
  end
end

beyonce = Person.new("Beyonce")
beyonce.get_married("Jay-Z")
puts beyonce.name

Now, run the file one more time, and you'll see that not only is our custom error message printed out but the program continues to run and will execute the puts beyonce.name line:

you must give the get_married method an argument of an instance of the person class!
Beyonce

View Custom Errors on Learn.co and start learning to code for free.