Making our way along through the Ruby Exception Handling series, today we'll explore the RangeError
within Ruby. Simply put, the RangeError
is raised when a numerical value is provided to a Ruby method that falls outside the allowed range of values.
We'll take some time in this article to fully explore the RangeError
including where it resides within the Ruby Exception
class hierarchy, along with a few code examples to illustrate some possible ways to raise RangeErrors
yourself, so let's get going!
Exception
class, or a subclass therein.StandardError
is a direct descendant of the Exception
class, and is also a superclass with many descendants of its own.RangeError
is a direct descendant of the StandardError
class.To further explore the RangeError
let's start right out with some example code:
# Output the first (2) items in the array.
['Alice', 'Bob', 'Cindy', 'Dave'].first(2)
This produces the expected result of the first two values:
["Alice", "Bob"]
However, like many built-in Ruby methods, the first
method we're using on our Array
expects the passed value to fall within a certain range. Like most programming languages, Ruby's Integer
value is four bytes in length, allowing the maximum value of an Integer
to be 2,147,483,647
. Many methods in Ruby, including Array.first
that we used above, expect any passed numeric argument to be a valid Integer
(or long
), meaning the value cannot exceed that 2.14 million maximum.
So, what happens if we pass a number that's just a bit larger than the maximum allowed for the Integer
to the Array.first
method?
# Output the first (2147483648) items in the array.
['Alice', 'Bob', 'Cindy', 'Dave'].first(2147483648)
Ruby isn't happy about this and it raises a RangeError
, indicating the value we've provided is larger than an Integer
(long
), and is therefore a bignum
:
RangeError: bignum too big to convert into `long'
That's all well and good, and we can see how built-in Ruby methods might raise a RangeError
, but what about raising a RangeError
in our own code? When is it appropriate to use that type of error? To illustrate, we've created a Book
class with three attributes
: author
, page_count
, and title
:
def print_exception(exception, explicit)
puts "[#{explicit ? 'EXPLICIT' : 'INEXPLICIT'}] #{exception.class}: #{exception.message}"
puts exception.backtrace.join("\n")
endclass Book
attr_accessor :author, :page_count, :titledef initialize(args = {})
@author = args[:author]
@page_count = args[:page_count]
@title = args[:title]
end
end
Simple enough. Now we can create an instance of our Book
class within the create_book
function:
def create_book
begin
# Create a new book
book = Book.new(author: 'Patrick Rothfuss', page_count: 662, title: 'The Name of the Wind')
# Output class type.
puts book
# Output fields.
puts book.author
puts book.title
puts book.page_count
rescue RangeError => e
print_exception(e, true)
rescue => e
print_exception(e, false)
end
end
This works just as expected, creating our book object and outputting the attribute data for it:
#<Book:0x000000028457b8>
Patrick Rothfuss
The Name of the Wind
662
Our Book
class places no restrictions or limitations on the attribute fields we've provided. Specifically, what if we need to create an abridged book class, which would only be valid for books with a limited number of pages? For that, we've inherited our Book
class with the AbridgedBook
class seen below:
class AbridgedBook < Book
# Add page_count getter.
attr_reader :page_countdef initialize(args = {})
# Executes initialize for the parent superclass.
super(args)
# Invoke custom setter for passed page_count value, if applicable.
self.page_count = args[:page_count]
enddef page_count=(value)
min, max = 1, 1000
# Check if value outside allowed bounds.
if value < min || value > max
raise RangeError, "Value of [#{value}] outside bounds of [#{min}] to [#{max}]."
else
@page_count = value
end
end
end
We've also slightly changed the behavior of the page_count
attribute from the original Book
class it inherits from. We're using attr_reader
instead of attr_accessor
, so we only automatically generate the getter
method. For the setter
of page_count=
, we define the method ourselves so we can specify a valid range (1 to 1000) that the page_count
attribute can fall within. If it's outside these bounds, we raise a RangeError
. Lastly, we're using the super
method call, which calls the superclass matching method (in this case initialize
), so we don't need to redefine the assignment of repeated fields like author
and title
.
To make use of AbridgedBook
we have two functions, create_abridged_book
and create_invalid_abridged_book
. For create_abridged_book
we are creating a book record for The Stand by Stephen King, which is 823 pages, falling within the valid range of page_count
:
def create_abridged_book
begin
# Create a new book
book = AbridgedBook.new(author: 'Stephen King', page_count: 823, title: 'The Stand')
# Output class type.
puts book
# Output fields.
puts book.author
puts book.title
puts book.page_count
rescue RangeError => e
print_exception(e, true)
rescue => e
print_exception(e, false)
end
end
As expected, this works just fine and our AbridgedBook
instance is created and we spit out some info about it:
#<AbridgedBook:0x00000002845600>
Stephen King
The Stand
823
However, within the create_invalid_abridged_book
we can confirm that our range limitations for page_count
are working by creating an book instance for War and Peace, which is a whopping 1225 pages:
def create_invalid_abridged_book
begin
# Create a new book
book = AbridgedBook.new(author: 'Leo Tolstoy', page_count: 1225, title: 'War and Peace')
# Output class type.
puts book
# Output fields.
puts book.author
puts book.title
puts book.page_count
rescue RangeError => e
print_exception(e, true)
rescue => e
print_exception(e, false)
end
end
As we hoped, our AbridgedBook.page_count=
setter method catches this value and raises a RangeError
as we asked it to, informing us that 1225
is outside the bounds we specified:
[EXPLICIT] RangeError: Value of [1225] outside bounds of [1] to [1000].
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.