Why can't I rescue this custom error defined inside another class?

Viewed 153

I'm working in Rails 6. I've defined an error class inside the class that raises it:

class MyClass < ApplicationRecord
  ...

  class CustomError < StandardError
    attr_reader :param

    def initialize(param)
      @param = param
    end
  end
end

I try to rescue it in client code, but this doesn't catch it:

rescue MyClass::CustomError => e

On the other hand this raises uninitialized constant on CustomError, exactly as I'd expect:

rescue CustomError => e

What about this?

rescue MyClass::CustomError => e
  raise unless e.instance_of? MyClass::CustomError
  # do some real error handling
  ...
end

instance_of? doesn't recognise the error and it re-raises.

I've even tried:

class MyClass < ApplicationRecord
  ...
end

class MyClass::CustomError < StandardError
  attr_reader :para
  def initialize(param)
    @param = param
  end
end

That's no better.

2 things that have worked are both code smells:

  1. Defining the error outside the containing class, with no namespacing

  2. Rescuing a generic error: rescue => e

It also works fine in MiniTest:

e = assert_raises(MyClass::CustomError) do
  ...
end

It feels like the error class name CustomError simply isn't resolving to MyClass::CustomError in app code, but the client code needs the containing classname to resolve the class at all.

More likely though I'm just doing something a bit naive/stupid!

Additional info:

  • Ruby 2.7.1
  • Rails 6.0.3.3
  • The class raising the error and the class handling it are both model classes (a bit unusual)
  • The runtime class raising the error is a child of MyClass: MyChild < MyClass < ActiveRecord (also unusual)
1 Answers

For anyone who doesn't know, Rails 4.2 added Spring, which keeps the application in memory when you terminate it (to speed up start times), and Rails 5.2 added Bootsnap, which caches optimised code. Either of them can keep unchanged code hanging around and because weird bugs.

One of them just bit me. It seems Bootsnap was raising the error but not making it visible outside MyClass. Deleting the cache sorted it all out:

rm -rf tmp/cache/bootsnap-compile-cache/
rm tmp/cache/bootsnap-load-path-cache
Related