Ruby custom error classes: inheritance of the message attribute Ruby custom error classes: inheritance of the message attribute ruby ruby

Ruby custom error classes: inheritance of the message attribute


raise already sets the message so you don't have to pass it to the constructor:

class MyCustomError < StandardError  attr_reader :object  def initialize(object)    @object = object  endendbegin  raise MyCustomError.new("an object"), "a message"rescue MyCustomError => e  puts e.message # => "a message"  puts e.object # => "an object"end

I've replaced rescue Exception with rescue MyCustomError, see Why is it a bad style to `rescue Exception => e` in Ruby?.


Given what the ruby core documentation of Exception, from which all other errors inherit, states about #message

Returns the result of invoking exception.to_s. Normally this returns the exception’s message or name. By supplying a to_str method, exceptions are agreeing to be used where Strings are expected.

http://ruby-doc.org/core-1.9.3/Exception.html#method-i-message

I would opt for redefining to_s/to_str or the initializer. Here is an example where we want to know, in a mostly human readable way, when an external service has failed to do something.

NOTE: The second strategy below uses the rails pretty string methods, such as demodualize, which may be a little complicated and therefore potentially unwise to do in an exception. You could also add more arguments to the method signature, should you need.

Overriding #to_s Strategy not #to_str, it works differently

module ExternalService  class FailedCRUDError < ::StandardError    def to_s      'failed to crud with external service'    end  end  class FailedToCreateError < FailedCRUDError; end  class FailedToReadError < FailedCRUDError; end  class FailedToUpdateError < FailedCRUDError; end  class FailedToDeleteError < FailedCRUDError; endend

Console Output

begin; raise ExternalService::FailedToCreateError; rescue => e; e.message; end# => "failed to crud with external service"begin; raise ExternalService::FailedToCreateError, 'custom message'; rescue => e; e.message; end# => "failed to crud with external service"begin; raise ExternalService::FailedToCreateError.new('custom message'); rescue => e; e.message; end# => "failed to crud with external service"raise ExternalService::FailedToCreateError# ExternalService::FailedToCreateError: failed to crud with external service

Overriding #initialize Strategy

This is the strategy closest to implementations I've used in rails. As noted above, it uses the demodualize, underscore, and humanize ActiveSupport methods. But this could be easily removed, as in the previous strategy.

module ExternalService  class FailedCRUDError < ::StandardError    def initialize(service_model=nil)      super("#{self.class.name.demodulize.underscore.humanize} using #{service_model.class}")    end  end  class FailedToCreateError < FailedCRUDError; end  class FailedToReadError < FailedCRUDError; end  class FailedToUpdateError < FailedCRUDError; end  class FailedToDeleteError < FailedCRUDError; endend

Console Output

begin; raise ExternalService::FailedToCreateError; rescue => e; e.message; end# => "Failed to create error using NilClass"begin; raise ExternalService::FailedToCreateError, Object.new; rescue => e; e.message; end# => "Failed to create error using Object"begin; raise ExternalService::FailedToCreateError.new(Object.new); rescue => e; e.message; end# => "Failed to create error using Object"raise ExternalService::FailedCRUDError# ExternalService::FailedCRUDError: Failed crud error using NilClassraise ExternalService::FailedCRUDError.new(Object.new)# RuntimeError: ExternalService::FailedCRUDError using Object

Demo Tool

This is a demo to show rescuing and messaging of the above implementation. The class raising the exceptions is a fake API to Cloudinary. Just dump one of the above strategies into your rails console, followed by this.

require 'rails' # only needed for second strategy module ExternalService  class FailedCRUDError < ::StandardError    def initialize(service_model=nil)      @service_model = service_model      super("#{self.class.name.demodulize.underscore.humanize} using #{@service_model.class}")    end  end  class FailedToCreateError < FailedCRUDError; end  class FailedToReadError < FailedCRUDError; end  class FailedToUpdateError < FailedCRUDError; end  class FailedToDeleteError < FailedCRUDError; endend# Stub service representing 3rd party cloud storageclass Cloudinary  def initialize(*error_args)    @error_args = error_args.flatten  end  def create_read_update_or_delete    begin      try_and_fail    rescue ExternalService::FailedCRUDError => e      e.message    end  end  private def try_and_fail    raise *@error_args  endenderrors_map = [  # Without an arg  ExternalService::FailedCRUDError,  ExternalService::FailedToCreateError,  ExternalService::FailedToReadError,  ExternalService::FailedToUpdateError,  ExternalService::FailedToDeleteError,  # Instantiated without an arg  ExternalService::FailedCRUDError.new,  ExternalService::FailedToCreateError.new,  ExternalService::FailedToReadError.new,  ExternalService::FailedToUpdateError.new,  ExternalService::FailedToDeleteError.new,  # With an arg  [ExternalService::FailedCRUDError, Object.new],  [ExternalService::FailedToCreateError, Object.new],  [ExternalService::FailedToReadError, Object.new],  [ExternalService::FailedToUpdateError, Object.new],  [ExternalService::FailedToDeleteError, Object.new],  # Instantiated with an arg  ExternalService::FailedCRUDError.new(Object.new),  ExternalService::FailedToCreateError.new(Object.new),  ExternalService::FailedToReadError.new(Object.new),  ExternalService::FailedToUpdateError.new(Object.new),  ExternalService::FailedToDeleteError.new(Object.new),].inject({}) do |errors, args|  begin     errors.merge!( args => Cloudinary.new(args).create_read_update_or_delete)  rescue => e    binding.pry  endendif defined?(pp) || require('pp')  pp errors_mapelse  errors_map.each{ |set| puts set.inspect }end


Your idea is right, but the way you call it is wrong. It should be

raise MyCustomError.new(an_object, "A message")