Next on the docket of our Ruby Exception Handling series is the lovable TypeError
class. TypeErrors
are fairly simple and are raised when an object that was passed as an argument is not of the expected type.
Don't be surprised if you're seeing it in your code. According to The 2022 Airbrake Error Data Report, a TypeError occurs in about 47% of coding projects.
In this article, we'll look at TypeErrors
a bit closer and see where it resides in the Ruby Exception
class hierarchy. We'll also dig into a few simple, functional code examples to see how TypeErrors
are commonly raised in the hopes of helping you during your own coding endeavors, so let's get crackin'!
The Technical Rundown
- All Ruby exceptions are descendants of the
Exception
class, or a subclass therein. StandardError
is a direct descendant of theException
class, and is also a superclass with many descendants of its own.TypeError
is the direct descendant ofStandardError
.
When Should You Use It?
Raising a TypeError
is a rather simple affair -- it commonly occurs when trying to pass an incorrect data type to a method. Many API functions and methods expect a specific type of object to be passed as arguments, so any attempts to pass invalid types usually results in a failure, and sometimes even the raising of a TypeError
depending how that module was written.
To see TypeErrors
in action let's start with some example code. Below we have the full working code example, after which we'll break it down into easier chunks to see what's going on:
def execute_examples
array_example
Logging.line_separator
invalid_array_example
Logging.line_separator
string_example
Logging.line_separator
invalid_string_example
end
def array_example
begin
titles = [
"Do Androids Dream of Electric Sheep?",
"Something Wicked This Way Comes",
"The Hitchhiker's Guide to the Galaxy",
"Pride and Prejudice",
"Eats, Shoots & Leaves: The Zero Tolerance Approach to Punctuation",
]
# Sort array then grab first (4) records and output them to log.
Logging.log(titles.sort.first(4))
rescue TypeError => e
Logging.log(e)
rescue => e
Logging.log(e, { explicit: false })
end
end
def invalid_array_example
begin
titles = [
"Do Androids Dream of Electric Sheep?",
"Something Wicked This Way Comes",
"The Hitchhiker's Guide to the Galaxy",
"Pride and Prejudice",
"Eats, Shoots & Leaves: The Zero Tolerance Approach to Punctuation",
]
# Sort array then try to get first '4' records as string and output.
Logging.log(titles.sort.first('4'))
rescue TypeError => e
Logging.log(e)
rescue => e
Logging.log(e, { explicit: false })
end
end
def string_example
begin
title = "Hitchhiker's Guide to the Galaxy"
# Insert string at zero index.
Logging.log(title.insert(0, 'The '))
rescue TypeError => e
Logging.log(e)
rescue => e
Logging.log(e, { explicit: false })
end
end
def invalid_string_example
begin
title = "Hitchhiker's Guide to the Galaxy"
# Insert string at invalid '0' index.
Logging.log(title.insert('0', 'The '))
rescue TypeError => e
Logging.log(e)
rescue => e
Logging.log(e, { explicit: false })
end
end
# Execute examples.
execute_examples
module Logging
extend Utility
class << self
# Outputs +value+ to console.
# +args+ may include:
# +:explicit+ (Boolean) - Is +Exception+ class +value+ expected? [default: true]
# +:timestamp+ (Boolean) - Should timestamp be included? [default: false]
#
# Examples:
#
# Logging.log('My message') #=> "My message"
# Logging.log('My message', { timestamp: true} ) #=> "[12:00:05] My message"
#
# begin
# raise Exception.new('An exception!')
# rescue Exception => e
# Logging.log(e)
# end
# #=> (EXPLICIT) Exception: An exception!
# #=> (...backtrace...)
def log(value, args = {})
# Check if exception was explicit.
explicit = args[:explicit].nil? ? true : args[:explicit]
# Get timestamp if necessary.
timestamp = args[:timestamp] ? formatted_timestamp : ""
if value.is_a?(Exception)
# If +value+ is an +Exception+ type output formatted exception.
puts timestamp << formatted_exception(value, explicit)
elsif value.is_a?(String)
# If +value+ is a +String+ directly output
puts timestamp << value
else
# If +value+ is anything else output.
puts timestamp
puts value
end
end
# Output the specified +separator+ +count+ times to log.
# +args may include:
# +:count+ (Integer) - Number of characters to output. [default: 20]
# +:separator+ (String) - Character or string to duplicate and output. [default: '-']
def line_separator(args = {})
count = args[:count].nil? ? 20 : args[:count]
separator = args[:separator].nil? ? '-' : args[:separator]
# Concatenate and output.
puts separator * count
end
private
def formatted_exception(exception, explicit)
# Set explicit or inexplicit tag.
output = "(#{explicit ? 'EXPLICIT' : 'INEXPLICIT'}) "
# Add class and message.
output << "#{exception.class}: #{exception.message}\n"
# Append backtrace with leading tabs.
output << "\t" << exception.backtrace.join("\n\t")
# Return output string.
output
end
def formatted_timestamp
"[#{Time.now.strftime("%T")}] "
end
end
end
To start we have our array_example
method where we've listed a few book titles in an array and we want to #sort
that array alphabetically and then grab the first four elements using the #first
method:
def array_example
begin
titles = [
"Do Androids Dream of Electric Sheep?",
"Something Wicked This Way Comes",
"The Hitchhiker's Guide to the Galaxy",
"Pride and Prejudice",
"Eats, Shoots & Leaves: The Zero Tolerance Approach to Punctuation",
]
# Sort array then grab first (4) records and output them to log.
Logging.log(titles.sort.first(4))
rescue TypeError => e
Logging.log(e)
rescue => e
Logging.log(e, { explicit: false })
end
end
As expected this works just fine and outputs the first four alphabetized titles:
Do Androids Dream of Electric Sheep?
Eats, Shoots & Leaves: The Zero Tolerance Approach to Punctuation
Pride and Prejudice
Something Wicked This Way Comes
However, now in our invalid_array_example
method we're going to try something much the same, but we're accidentally passing the String
value of '4'
instead of the Integer
value of 4
to the #first
method of our array:
def invalid_array_example
begin
titles = [
"Do Androids Dream of Electric Sheep?",
"Something Wicked This Way Comes",
"The Hitchhiker's Guide to the Galaxy",
"Pride and Prejudice",
"Eats, Shoots & Leaves: The Zero Tolerance Approach to Punctuation",
]
# Sort array then try to get first '4' records as string and output.
Logging.log(titles.sort.first('4'))
rescue TypeError => e
Logging.log(e)
rescue => e
Logging.log(e, { explicit: false })
end
end
As it turns, if we look directly at the source code of Ruby's #first
method we see that it requires an integer (int
) parameter for the first value (argc
):
rb_ary_first(int argc, VALUE *argv, VALUE ary)
{
if (argc == 0) {
if (RARRAY_LEN(ary) == 0) return Qnil;
return RARRAY_AREF(ary, 0);
}
else {
return ary_take_first_or_last(argc, argv, ary, ARY_TAKE_FIRST);
}
}
Therefore, our above Ruby code raises a TypeError
for our troubles, informing us that we cannot implicitly convert our String
value to an Integer
that is required:
(EXPLICIT) TypeError: no implicit conversion of String into Integer
As it happens there are near-countless methods in Ruby that will raise TypeErrors
if given improper argument data types. Rather than an array let's try a String
using the #insert
method, which expects an Integer
as the first argument to indicate the index
of where the second argument should be inserted into the targetted string. Here we're adding the missing word 'The'
to our title then outputting the result:
def string_example
begin
title = "Hitchhiker's Guide to the Galaxy"
# Insert string at zero index.
Logging.log(title.insert(0, 'The '))
#=> The Hitchhiker's Guide to the Galaxy
rescue TypeError => e
Logging.log(e)
rescue => e
Logging.log(e, { explicit: false })
end
end
That works just fine, but once again if we pass a non-integer value the #insert
method doesn't know what to do. The invalid_string_example
method throws another TypeError
just as we saw before:
def invalid_string_example
begin
title = "Hitchhiker's Guide to the Galaxy"
# Insert string at invalid '0' index.
Logging.log(title.insert('0', 'The '))
#=> (EXPLICIT) TypeError: no implicit conversion of String into Integer
rescue TypeError => e
Logging.log(e)
rescue => e
Logging.log(e, { explicit: false })
end
end
To get the most out of your own applications and to fully manage any and all Ruby Exceptions, check out the Airbrake Ruby exception handling tool, offering real-time alerts and instantaneous insight into what went wrong with your Ruby code, including integrated support for a variety of popular Ruby gems and frameworks.
Note: We published this post in June 2017 and recently updated it in March 2022.