it-swarm.com.de

Ruby benutzerdefinierte Fehlerklassen: Vererbung des Nachrichtenattributs

Ich kann anscheinend nicht viele Informationen zu benutzerdefinierten Ausnahmeklassen finden.

Was ich weiß

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

class MyCustomError < StandardError
end

Auf diese Weise können Sie es erhöhen mit:

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 message -Attribut von der übergeordneten Klasse erben. Ich habe gelesen zu diesem Thema dass @message ist keine Instanzvariable der Ausnahmeklasse, daher mache ich mir Sorgen, dass meine Vererbung nicht funktioniert.

Kann mir jemand mehr Details dazu geben? Wie würde ich eine benutzerdefinierte Fehlerklasse mit einem object -Attribut implementieren? Ist das Folgende 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 Vorgehensweise?

90
MarioDS

raise legt die Nachricht bereits fest, sodass 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 rescue Exception Durch rescue MyCustomError Ersetzt, siehe Warum ist es ein schlechter Stil, Exception => e in Ruby zu retten? .

119
Stefan

In Anbetracht dessen, was die Ruby Kerndokumentation von Exception, von der alle anderen Fehler erben, über #message Aussagt

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 wird vereinbart, dass Ausnahmen verwendet werden, wenn Zeichenfolgen erwartet werden.

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

Ich würde mich dafür entscheiden, to_s/to_str Oder den Initialisierer neu zu definieren. Hier ist ein Beispiel, in dem wir auf eine meist vom Menschen lesbare Weise wissen möchten, wann ein externer Dienst etwas nicht getan hat.

ANMERKUNG: Die zweite Strategie verwendet die hübschen String-Methoden Rails= wie demodualize, die möglicherweise etwas kompliziert und daher in Ausnahmefällen möglicherweise unklug sind Fügen Sie der Methodensignatur weitere Argumente hinzu, falls erforderlich.

#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

Strategie #initialize überschreiben

Dies ist die Strategie, die den Implementierungen, die ich in Rails verwendet habe, am nächsten kommt. Wie oben erwähnt, werden die Methoden demodualize, underscore und humanizeActiveSupport verwendet. Dies könnte jedoch wie in der vorherigen Strategie leicht beseitigt 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 versendet wird. Die Klasse, die die Ausnahmen auslöst, ist eine gefälschte API für Cloudinary. Entleere einfach eine der oben genannten Strategien in deine Rails Konsole, 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
8
Chad M

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

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

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 es nicht funktioniert, wenn Sie attr_accessor :message Nicht deklarieren. Wenn Sie sich mit dem Problem des OP befassen, können Sie die Nachricht auch als zusätzliches Argument übergeben und alles speichern, was Sie möchten. Der entscheidende Teil scheint, #message zu überschreiben.

2
Huliax