Je définis une exception personnalisée sur un modèle dans Rails comme une sorte d'exception wrapper: (begin[code]rescue[raise custom exception]end
)
Lorsque je lève l'exception, je voudrais lui transmettre quelques informations sur a) l'instance du modèle dont les fonctions internes déclenchent l'erreur, et b) l'erreur qui a été interceptée.
Il s'agit d'une méthode d'importation automatisée d'un modèle qui est renseigné par POST depuis une source de données étrangère.
tldr; Comment passer des arguments à une exception, étant donné que vous définissez vous-même l'exception? J'ai une méthode d'initialisation sur cette exception, mais la syntaxe raise
semble accepter uniquement une classe et un message d'exception, aucun paramètre facultatif qui est transmis au processus d'instanciation.
créez une instance de votre exception avec new:
class CustomException < StandardError
def initialize(data)
@data = data
end
end
# => nil
raise CustomException.new(bla: "blupp")
# CustomException: CustomException
class FooError < StandardError
attr_reader :foo
def initialize(foo)
super
@foo = foo
end
end
C'est le meilleur moyen si vous suivez le Rubocop Style Guide et passez toujours votre message comme deuxième argument à raise
:
raise FooError.new('foo'), 'bar'
Vous pouvez obtenir foo
comme ceci:
rescue FooError => error
error.foo # => 'foo'
error.message # => 'bar'
Si vous souhaitez personnaliser le message d'erreur, écrivez:
class FooError < StandardError
attr_reader :foo
def initialize(foo)
super
@foo = foo
end
def message
"The foo is: #{foo}"
end
end
Cela fonctionne bien si foo
est requis. Si vous voulez que foo
soit un argument facultatif, continuez à lire.
raise
Comme le dit Rubocop Style Guide , le message et la classe d'exception doivent être fournis sous forme d'arguments distincts, car si vous écrivez:
raise FooError.new('bar')
Et si vous voulez passer une trace à raise
, il n'y a aucun moyen de le faire sans passer deux fois le message:
raise FooError.new('bar'), 'bar', other_error.backtrace
Comme cette réponse le dit, vous devrez passer une trace arrière si vous souhaitez relancer une exception en tant que nouvelle instance avec la même trace arrière et un message ou des données différents.
FooError
Le nœud du problème est que si foo
est un argument facultatif, il existe deux façons différentes de lever des exceptions:
raise FooError.new('foo'), 'bar', backtrace # case 1
et
raise FooError, 'bar', backtrace # case 2
et nous voulons que FooError
fonctionne avec les deux.
Dans le cas 1, puisque vous avez fourni une instance d'erreur plutôt qu'une classe, raise
définit 'bar'
Comme message de l'instance d'erreur.
Dans le cas 2, raise
instancie FooError
pour vous et passe 'bar'
Comme seul argument, mais il ne définit pas le message après l'initialisation comme dans le cas 1. Pour définir le message, vous devez appeler super
dans FooError#initialize
avec le message comme seul argument.
Ainsi, dans le cas 1, FooError#initialize
Reçoit 'foo'
Et dans le cas 2, il reçoit 'bar'
. Il est surchargé et il n'y a en général aucun moyen de différencier ces cas. Il s'agit d'un défaut de conception dans Ruby. Donc, si foo
est un argument facultatif, vous avez trois choix:
(a) acceptez que la valeur passée à FooError#initialize
peut être foo
ou un message.
(b) Utilisez uniquement le style de cas 1 ou de cas 2 avec raise
mais pas les deux.
(c) Faites de foo
un argument de mot-clé.
Si vous ne voulez pas que foo
soit un argument de mot clé, je recommande (a) et mon implémentation de FooError
ci-dessus est conçue pour fonctionner de cette façon.
Si vous raise
a FooError
en utilisant le style de casse 2, la valeur de foo
est le message, qui est implicitement transmis à super
. Vous aurez besoin d'une super(foo)
explicite si vous ajoutez d'autres arguments à FooError#initialize
.
Si vous utilisez un argument de mot clé (h/t réponse de Lemon Cat ) alors le code ressemble à:
class FooError < StandardError
attr_reader :foo
def initialize(message, foo: nil)
super(message)
@foo = foo
end
end
Et l'augmentation ressemble à:
raise FooError, 'bar', backtrace
raise FooError(foo: 'foo'), 'bar', backtrace
Voici un exemple de code ajoutant un code à une erreur:
class MyCustomError < StandardError
attr_reader :code
def initialize(code)
@code = code
end
def to_s
"[#{code}] #{super}"
end
end
Et pour l'augmenter: raise MyCustomError.new(code), message
TL; DR 7 ans après cette question, je pense que la bonne réponse est:
class CustomException < StandardError
attr_reader :extra
def initialize(message=nil, extra: nil)
super(message)
@extra = extra
end
end
# => nil
raise CustomException.new('some message', extra: "blupp")
ATTENTION: vous obtiendrez des résultats identiques avec:
raise CustomException.new(extra: 'blupp'), 'some message'
mais c'est parce que Exception#exception(string)
fait un #rb_obj_clone
sur self
, puis appelle exc_initialize
(qui n'appelle PAS CustomException#initialize
. De - error.c :
static VALUE
exc_exception(int argc, VALUE *argv, VALUE self)
{
VALUE exc;
if (argc == 0) return self;
if (argc == 1 && self == argv[0]) return self;
exc = rb_obj_clone(self);
exc_initialize(argc, argv, exc);
return exc;
}
Dans le dernier exemple de #raise
Ci-dessus, un CustomException
sera raise
d avec message
réglé sur "un message" et extra
mis à "blupp" (car il s'agit d'un clone) mais [~ # ~] deux [~ # ~] CustomException
sont effectivement créé: le premier par CustomException.new
, et le second par #raise
appelant #exception
sur la première instance de CustomException
, ce qui crée un second cloné CustomException
.
Ma version de remix de danse étendue de pourquoi est à: https://stackoverflow.com/a/56371923/529948