Custom exception inheritance
It’s a good idea to define custom exception classes in Ruby libraries to allow users to match against specific library exceptions. For example, if you want to rescue a Phlex name error, you can match against Phlex::NameError
.
We also want to allow people to match with varying levels of precision. For example, we should be able to rescue Phlex::Error
to catch any of our libraries custom errors. We can do this by creating a base Error
class and having our other exceptions inherit from it.
module Phlex
Error = Class.new(StandardError)
NameError = Class.new(Error)
ArgumentError = Class.new(Error)
end
Phlex::NameError
and Phlex::ArgumentError
are both Phlex::Errors
so we can match either of these by rescuing Phlex::Error
.
But what if we want to give our errors semantic meaning? Phlex::NameError
should be a NameError
and Phlex::ArgumentError
should be an ArgumentError
. We could adjust the errors to inherit from the semantic counterparts, but in doing so, we’d lose the ability to rescue all Phlex errors as Phlex::Error
.
module Phlex
Error = Class.new(StandardError)
NameError = Class.new(NameError)
ArgumentError = Class.new(ArgumentError)
end
Phlex::ArgumentError
is now a semantic ArgumentError
and will match when rescuing ArgumentError
, but it won’t match when rescuing Phlex::Error
.
What I like to do is define an Error
module and mix it into my custom exceptions. Here’s what that looks like:
module Phlex
Error = Module.new
NameError = Class.new(NameError) { include Error }
ArgumentError = Class.new(ArgumentError) { include Error }
StandardError = Class.new(StandardError) { include Error }
end
This pattern allows us to inherit from semantic Ruby exceptions while maintaining our ability to match against all library errors. Phlex::ArgumentError
, for example, is still an ArgumentError
, but it’s also a Phlex::Error
.
We can’t raise a Phlex::Error
itself because it’s just a module, but we can define Phlex::StandardError
instead.