Benutzerdefinierte Ruby-Fehlerklassen: Vererbung des Nachrichtenattributs

95

Ich kann anscheinend nicht viele Informationen zu benutzerdefinierten Ausnahmeklassen finden.

Was ich weiß

Sie können Ihre benutzerdefinierte Fehlerklasse deklarieren und von ihr erben lassen StandardError, sodass sie rescued sein kann:

class MyCustomError < StandardError
end

Dies ermöglicht es Ihnen, es zu erhöhen, indem Sie:

raise MyCustomError, "A message"

und später erhalten Sie diese Nachricht bei der Rettung

rescue MyCustomError => e
  puts e.message # => "A message"

Was ich nicht weiß

Ich möchte meiner Ausnahme einige benutzerdefinierte Felder geben, aber ich möchte das messageAttribut von der übergeordneten Klasse erben . Ich fand heraus , das Lesen zu diesem Thema , das @messagenicht eine Instanzvariable der Ausnahmeklasse, so dass ich besorgt bin , dass mein Erbe wird nicht funktionieren.

Kann mir jemand mehr Details dazu geben? Wie würde ich eine benutzerdefinierte Fehlerklasse mit einem objectAttribut implementieren ? Ist folgendes richtig:

class MyCustomError < StandardError
  attr_reader :object
  def initialize(message, object)
    super(message)
    @object = object
  end
end

Und dann:

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

bekommen:

rescue MyCustomError => e
  puts e.message # => "A message"
  puts e.object # => anObject

Wird es funktionieren, und wenn ja, ist dies die richtige Art, Dinge zu tun?

MarioDS
quelle
3
Tu es nicht rescue Exception => e. Es ist breiter als die Standardeinstellung, von rescue => eder aus StandardErroralles erfasst wird, einschließlich Strg + C. Ich würde es tun rescue MyCustomError => e.
Ryan Taylor
1
@ RyanTaylor Ich habe meine Frage für den richtigen Ansatz bearbeitet.
MarioDS

Antworten:

121

raise Setzt die Nachricht bereits so, dass Sie sie nicht an den Konstruktor übergeben müssen:

class MyCustomError < StandardError
  attr_reader :object

  def initialize(object)
    @object = object
  end
end

begin
  raise MyCustomError.new("an object"), "a message"
rescue MyCustomError => e
  puts e.message # => "a message"
  puts e.object # => "an object"
end

Ich habe ersetzt rescue Exceptiondurch rescue MyCustomError, siehe Warum ist es ein schlechter Stil, Exception => e in Ruby zu retten? .

Stefan
quelle
Ich werde Ihre Antwort akzeptieren, weil Sie mir die gesamte Syntax gezeigt haben. Vielen Dank!
MarioDS
1
Hier machen wir rescue Exception, aber warum nicht rescue MyCustomError?
Dfr
Zu Ihrer Information, wenn das erste Argument, Objekt, eine Option ist und raise MyCustomError, "a message"ohne new"eine Nachricht" nicht gesetzt wird.
Hiroshi
Gibt es eine Möglichkeit, die angezeigte Nachricht in unserer benutzerdefinierten Ausnahmeklasse irgendwie abzurufen?
CyberMew
@CyberMew was meinst du? Was möchten Sie tun?
Stefan
10

In Anbetracht dessen, worüber die Ruby-Core-Dokumentation Exception, von der alle anderen Fehler erben, aussagt#message

Gibt das Ergebnis des Aufrufs von exception.to_s zurück. Normalerweise gibt dies die Nachricht oder den Namen der Ausnahme zurück. Durch die Angabe einer to_str-Methode erklären sich Ausnahmen damit einverstanden, dort verwendet zu werden, wo Strings erwartet werden.

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

Ich würde mich für eine Neudefinition to_s/ to_stroder den Initialisierer entscheiden. Hier ist ein Beispiel, in dem wir auf meist menschlich lesbare Weise wissen möchten, wann ein externer Dienst etwas nicht getan hat.

HINWEIS: In der folgenden zweiten Strategie werden die Rails-Methoden für hübsche Zeichenfolgen verwendet, z. B. demodualize, die möglicherweise etwas kompliziert und daher in einer Ausnahme möglicherweise unklug sind. Sie können der Methodensignatur bei Bedarf auch weitere Argumente hinzufügen.

#To_s Strategie überschreiben nicht #to_str, es funktioniert anders

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; end
end

Konsolenausgabe

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

# Initialize Strategy überschreiben

Dies ist die Strategie, die den Implementierungen am nächsten kommt, die ich in Schienen verwendet habe. Wie oben erwähnt, verwendet es das demodualize, underscoreundhumanize ActiveSupport Methoden. Dies könnte jedoch wie in der vorherigen Strategie leicht entfernt werden.

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; end
end

Konsolenausgabe

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 NilClass

raise ExternalService::FailedCRUDError.new(Object.new)
# RuntimeError: ExternalService::FailedCRUDError using Object

Demo-Tool

Dies ist eine Demo, die zeigt, wie die oben genannte Implementierung gerettet und übermittelt wird. Die Klasse, die die Ausnahmen auslöst, ist eine gefälschte API für Cloudinary. Legen Sie einfach eine der oben genannten Strategien in Ihre Rails-Konsole ein, gefolgt von dieser.

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; end
end

# Stub service representing 3rd party cloud storage
class 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
  end
end

errors_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
  end
end

if defined?(pp) || require('pp')
  pp errors_map
else
  errors_map.each{ |set| puts set.inspect }
end
Chad M.
quelle
6

Ihre Idee ist richtig, aber die Art, wie Sie sie nennen, ist falsch. Es sollte sein

raise MyCustomError.new(an_object, "A message")
sawa
quelle
Okay, ich dachte, dass die Nachricht, die Sie gegeben haben, ein zweiter Parameter für das raiseSchlüsselwort ist oder so.
MarioDS
Sie haben initializezwei Argumente neu definiert . newübergibt die Argumente an initialize.
Sawa
Oder Sie können die Klammern weglassen.
Sawa
Ich verstehe das ein bisschen, aber das Poster des Themas, auf das ich in meiner Frage verlinkt habe, macht es so : raise(BillRowError.new(:roamingcalls, @index), "Roaming Calls field missing"). Also ruft er raisemit zwei Parametern auf: einem neuen BillRowErrorObjekt und seiner Nachricht. Ich bin nur verwirrt von der Syntax ... In anderen Tutorials sehe ich das immer so:raise Error, message
MarioDS
1
Das Problem liegt nicht darin, an wie viele Argumente Sie übergeben raise. das ist ziemlich flexibel. Das Problem ist, dass Sie initializezwei Argumente definiert und nur eines angegeben haben. Schauen Sie in Ihr Beispiel. BillRowError.new(:roamingcalls, @index)wird zwei Argumente gegeben.
Sawa
4

Ich wollte etwas Ähnliches machen. Ich wollte ein Objekt an #new übergeben und die Nachricht basierend auf einer Verarbeitung des übergebenen Objekts festlegen. Folgendes funktioniert.

class FooError < StandardError
  attr_accessor :message # this is critical!
  def initialize(stuff)
    @message = stuff.reverse
  end
end

begin
  raise FooError.new("!dlroW olleH")
rescue FooError => e
  puts e.message #=> Hello World!
end

Beachten Sie, dass attr_accessor :messagees nicht funktioniert , wenn Sie nicht deklarieren . Wenn Sie das Problem des OP beheben möchten, können Sie die Nachricht auch als zusätzliches Argument übergeben und alles speichern, was Sie möchten. Der entscheidende Teil scheint darin zu bestehen, #message zu überschreiben.

Huliax
quelle