Ruby will intentionally hide some errors & exceptions from you.
Sometimes this can be useful.
Like when using the Kernel#loop
method with a block, the loop
will stop when a StopIteration
exception is raised.
But other times this can make your debugging sessions a lot harder.
Let’s see some examples!
Hidden Exception: Comparable Module + <=> Method
The first example involves the Comparable
module & the <=>
method.
Here’s the example:
class MyObject attr_accessor :value include Comparable def initialize(value) @value = value end def <=>(other) raise ArgumentError, "can't compare #{other.class} with #{self.class}" unless other.is_a?(MyObject) value <=> other.valuee end end mo1 = MyObject.new(10) mo2 = MyObject.new(10) p mo1 == mo2
Let’s talk about this example.
First:
We have a class named MyObject
, with one attr_accessor value
, and the inclusion of the Comparable
module, which adds comparison methods (like ==
, <
, >
) to our class.
These comparison methods are based on the <=>
method.
Just like Enumerable methods are based on the each
method.
Then:
We are creating two objects (MyObject.new
) with the same value (10
).
Notice that even if they have the same values they are different objects, this is important.
Now if we compare these two objects mo1
& mo2
we get false
…
Why?
Because we have an error in our <=>
method, but Ruby is hiding that error!
Look closely…
Can you spot the error?
If you found it good job! If not that’s ok 🙂
Here it is:
value <=> other.valuee
See this valuee
?
Turns out we have a typo!
Normally we would get a NoMethodError
exception & we would know what the problem is pretty quickly. But not in this example.
The good news is that since Ruby 2.3 this has changed. You can see the error now since it’s no longer hidden.
Another reason to upgrade if you are still running older Ruby versions.
Hidden Exception: Numeric Object + Coercion Method
Another example of a hidden exception is with Numeric
objects (Float
, Integer
) plus the coerce
method.
Here’s an example:
class MyObject attr_accessor :value def initialize(value) @value = value end def +(other) other = MyObject.new(other) if other.kind_of?(Numeric) value + other.value end def coerce(other) mo = MyObject.new mo.valuee = other [mo, self] end end mo1 = MyObject.new 10 mo2 = MyObject.new 10 p mo1 + mo2 # 20 p mo1 + 20 # 30
This is another MyObject
class, but with new methods, +
& coerce
.
The coerce
method allows you to work with two incompatible types & transform them into the same type so they can work together.
In this case our class represents some numeric value, which could be a number of seconds, a price or anything like that…
And we want to be able to do these kind of operations:
mo1 + 20 20 + mo1
The first one (mo1 + 20
) is easy since we control the +
method in our class.
But what about the Integer
class?
We could change Integer’s +
method to implement this, but that’s probably not a good idea 🙂
The solution?
Implement the coerce
method in your own class.
One thing the +
method on Integer
will do is to check if your object implements this method, and if it does it will call it.
Now, remember the code example at the start of this section?
If we try to do this:
20 + mo1
We want to see 30
, because the value for mo1
is 10
. But what we see is this:
MyObject can't be coerced into Fixnum
Same problem as before!
An error is being hidden from us inside the coerce
method.
This: mo.valuee = other
Again we have a typo, but that’s not what the error is saying!
MyObject can't be coerced into Fixnum
I have good news for you, this behavior is changing in Ruby 2.5, so that will be another hidden error going away so you don’t have to worry about it.
Summary
As you can see these are good examples on why you want to avoid hiding exceptions. You can learn more about exceptions in my book, Ruby Deep Dive.
I would appreciate if you shared this post with your friends so more people can see it.
Thanks for reading!