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.