- Create and raise custom error messages in Ruby
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're creating a new feature for an application being worked on by a team of other developers. If another developer uses some of our code incorrectly (such as calling a method with the wrong kind of data), it would be helpful for them to know what they did wrong so they can fix it. We can use custom error messages and custom error handling to save the day!
By defining custom error messages and handling, we can show other developers a specific error message in the event they use some of our code incorrectly. In this reading and the following lab, we'll practice building simple custom errors.
Code along with the instructions below.
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
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
Let's look at the example of our Person
class and its #get_married
method.
In lib/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 (that is, a class without a #partner=
method defined).
For example, at the bottom of our lib/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 lib/custom_errors.rb
file with the
ruby lib/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!
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
# rest of class...
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.
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)
if person.is_a?(Person)
self.partner = person
person.partner = self
else
raise PartnerError
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...
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.
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
.
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)
if person.is_a?(Person)
self.partner = person
person.partner = self
else
begin
raise PartnerError
rescue PartnerError => error
puts error.message
end
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)
if person.is_a?(Person)
self.partner = person
person.partner = self
else
begin
raise PartnerError
rescue PartnerError => error
puts error.message
end
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
Run learn test
with the code above, and the tests should all pass!
Adding custom errors to our Ruby classes makes them much nicer for other developers to work with. We've extended the API (the interface) of our classes to include more helpful messages to other developers who are using our code that indicate not only what the issue is, but how they can fix it by using our code in the way it's intended to be used.
We also saw how to use the begin/rescue
syntax as another form of control
flow to handle exceptions gracefully.