Ruby Is Hiding Errors From You!

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!