Classes d’erreur personnalisées Ruby: inheritance de l’atsortingbut de message

Je n’arrive pas à trouver beaucoup d’informations sur les classes d’exceptions personnalisées.

Ce que je sais

Vous pouvez déclarer votre classe d’erreur personnalisée et la laisser hériter de StandardError , vous pouvez donc la rescue :

 class MyCustomError < StandardError end 

Cela vous permet de l’élever en utilisant:

 raise MyCustomError, "A message" 

et plus tard, obtenez ce message lors du sauvetage

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

Ce que je ne sais pas

Je veux donner à mon exception des champs personnalisés, mais je veux hériter de l’atsortingbut de message de la classe parente. J’ai découvert en lisant sur ce sujet que @message n’est pas une variable d’instance de la classe des exceptions, alors je crains que mon inheritance ne fonctionne pas.

Quelqu’un peut-il me donner plus de détails à ce sujet? Comment pourrais-je implémenter une classe d’erreur personnalisée avec un atsortingbut d’ object ? Est-ce que c’est correct:

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

Et alors:

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

obtenir:

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

Cela fonctionnera-t-il, et si c’est le cas, est-ce la bonne façon de faire les choses?

raise définit déjà le message pour que vous n’ayez pas à le transmettre au constructeur:

 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 

J’ai remplacé rescue Exception de rescue MyCustomError par le rescue MyCustomError , voir Pourquoi est-ce un mauvais style de «sauver Exception => e` dans Ruby? .

Compte tenu de la documentation de base de Exception de Exception , à partir de laquelle toutes les autres erreurs héritent, il est indiqué à propos de #message

Renvoie le résultat de l’invocation de l’exception.to_s. Normalement, cela renvoie le message ou le nom de l’exception. En fournissant une méthode to_str, les exceptions acceptent d’être utilisées lorsque des chaînes sont attendues.

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

to_s pour redéfinir to_s / to_str ou l’initialiseur. Voici un exemple où nous voulons savoir, d’une manière essentiellement lisible par l’homme, lorsqu’un service externe n’a pas réussi à faire quelque chose.

REMARQUE: La deuxième stratégie ci-dessous utilise les méthodes jolie chaîne de rails, telles que la demodualize , qui peut être un peu compliquée et donc potentiellement imprudente à faire dans une exception. Vous pouvez également append d’autres arguments à la signature de la méthode, si vous en avez besoin.

Surmonter #to_s Stratégie pas #to_str, cela fonctionne différemment

 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 

Sortie de console

 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 

Remplacement de la stratégie d'initialisation

C'est la stratégie la plus proche des implémentations que j'ai utilisées dans les rails. Comme indiqué ci-dessus, il utilise les demodualize , underscore et ActiveSupport . Mais cela pourrait être facilement supprimé, comme dans la stratégie précédente.

 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 

Sortie de console

 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 

Outil de démonstration

Ceci est une démo pour montrer le sauvetage et la messagerie de l'implémentation ci-dessus. La classe levant les exceptions est une fausse API vers Cloudinary. Il suffit de vider l’une des stratégies ci-dessus dans votre console de rails, suivie de cela.

 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 

Votre idée est juste, mais la façon dont vous l’appelez est fausse. CA devrait etre

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

Je voulais faire quelque chose de similaire. Je voulais passer un object à #new et avoir le jeu de messages basé sur un traitement de l’object passé. Les travaux suivants

 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 

Notez que si vous ne déclarez pas attr_accessor :message alors cela ne fonctionnera pas. En abordant le problème du PO, vous pouvez également transmettre le message comme argument supplémentaire et stocker tout ce que vous voulez. La partie cruciale semble être #message.