Today I was thinking about a way to define custom exceptions with a predefined error message. For example, instead of doing this:
raise MyError, "Something went wrong."
We want to simply do:
raise MyError
This could be useful because if we need to raise that same exception again and again, we don’t have to specify the error message every time.
Well, how can we do that?
I spent all day figuring out the best way, actually doing very bad things – I’ve even attempted to monkey-patch the Kernel module!
So – believe me – it’s not as simple as it appears to be. Or, at least, I thought this until I stumbled across this article (dead link).
In short, you just need to override Exception#message
.
For example:
class MyError < Exception
def message
"a predefined message"
end
end
raise MyError
# => MyError: a predefined message
Quick note: I’m inheriting from StandardError
, not Exception
, because extending the Exception
class in Ruby is considered really bad. Please don’t inherit from it: see here and here for the reason (in few words it’s because you may catch errors that are not meant to be catched, such as SyntaxError
).
Of course you could also create a module with your own exceptions in it:
module CustomError
class AnError < StandardError
def message
"A more specific error"
end
end
class AnotherError < StandardError
def message
"just another error"
end
end
end
Or even a subclass of your custom error class:
module CustomError
class Error < StandardError
def message
"default error"
end
end
class SpecificError < Error
def message
"a more specific error"
end
end
end
However, this is not very useful. What I find useful, though, is that you can bring shared pieces of information from the base class to the subclasses, which is IMO very desirable in error handling.
Since Exception#message
is nothing but an alias of exception.to_s
, we can call super
to get the superclass' message. For example, this is what I ended up doing:
module CustomError
class Error < StandardError
def initialize(msg=nil)
@message = msg
end
def message
"Message from main class: #{@message}."
end
end
class SpecificError < Error
def message
super + " We also got a specific error."
end
end
end
And here's the result:
raise CustomError::SpecificError, "fubar"
# => CustomError::SpecificError: Message from main class: fubar. We also got a specific error.
This demonstrates that we can potentially carry whatever information (i.e. instances of objects involved in the error) in order to better handle errors in our applications.
That's it.
As always, please feel free to share your thoughts by commenting below.