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
, de sorte qu'elle puisse être rescue
d:
class MyCustomError < StandardError
end
Cela vous permet de l'élever en utilisant:
raise MyCustomError, "A message"
et plus tard, recevez ce message lors du sauvetage
rescue MyCustomError => e
puts e.message # => "A message"
Ce que je ne sais pas
Je souhaite attribuer à mon exception des champs personnalisés, mais je souhaite hériter de l'attribut message
de la classe parente. J'ai découvert la lecture sur ce sujet que @message
n'est pas une variable d'instance de la classe d'exception, je crains donc que mon héritage ne fonctionne pas.
Quelqu'un peut-il me donner plus de détails à ce sujet? Comment implémenter une classe d'erreur personnalisée avec un attribut object
? Est-ce que ce qui suit 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 cela fonctionne, est-ce la bonne façon de faire les choses?
raise
définit déjà le message afin 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
avec rescue MyCustomError
, voir Pourquoi est-ce un mauvais style de `rescue Exception => e` en Ruby? .
Compte tenu de ce que la Ruby documentation de base de Exception
, dont héritent toutes les autres erreurs, énonce à propos de #message
Renvoie le résultat de l'appel de exception.to_s. Normalement, cela retourne 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
Je choisirais de redéfinir to_s
/to_str
ou l'initialiseur. Voici un exemple où nous voulons savoir, de manière essentiellement lisible par l'homme, lorsqu'un service externe n'a pas réussi à faire quelque chose.
NOTE: La deuxième stratégie ci-dessous utilise les jolies méthodes de chaîne Rails, telles que demodualize
, ce qui peut être un peu compliqué et donc potentiellement peu judicieux dans une exception. Vous pouvez également ajoutez plus d'arguments à la signature de la méthode, si vous en avez besoin.
Remplacement de la stratégie #to_s 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 la 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 #initialize
C'est la stratégie la plus proche des implémentations que j'ai utilisées dans Rails. Comme indiqué ci-dessus, il utilise les méthodes demodualize
, underscore
et humanize
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 la 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émonstration pour montrer le sauvetage et la messagerie de la mise en œuvre ci-dessus. La classe soulevant les exceptions est une fausse API pour Cloudinary. Il suffit de déposer l’une des stratégies ci-dessus dans votre Rails console, suivie de celle-ci).
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 bonne, 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 transmettre un objet à #new et définir l'ensemble des messages en fonction du traitement de l'objet transmis. 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 passer le message comme argument supplémentaire et stocker tout ce que vous voulez. La partie cruciale semble être primordiale # message.