Où définir des types d'erreur personnalisés dans Ruby et / ou Rails?

149

Existe-t-il une meilleure pratique pour définir des types d'erreur personnalisés dans une bibliothèque Ruby (gem) ou une application Ruby on Rails? Plus précisément:

  1. Quelle est leur place structurelle dans le projet? Un fichier séparé, en ligne avec la définition de module / classe appropriée, ailleurs?
  2. Y a-t- il des conventions qui établissent quand à et quand ne pas créer un nouveau type d'erreur?

Différentes bibliothèques ont différentes façons de faire les choses, et je n'ai pas remarqué de vrais modèles. Certaines bibliothèques utilisent toujours des types d'erreur personnalisés tandis que d'autres ne les utilisent pas du tout; certains ont toutes les erreurs d'extension StandardError tandis que d'autres ont des hiérarchies imbriquées; certains ne sont que des définitions de classe vides, d'autres ont toutes sortes d'astuces intelligentes.

Oh, et juste parce que j'ai envie d'appeler ces «types d'erreur» est un peu ambigu, ce que je veux dire, c'est ceci:

class AuthenticationError < StandardError; end
class InvalidUsername < AuthenticationError; end
coreyward
la source

Réponses:

219

Pour les gemmes

J'ai vu plusieurs fois que vous définissez les exceptions de cette manière:

gem_dir / lib / gem_name / exceptions.rb

et défini comme:

module GemName

  class AuthenticationError < StandardError; end
  class InvalidUsername < AuthenticationError; end

end

un exemple de ceci serait quelque chose comme ça dans httparty

Pour Ruby on Rails

Mettez-les dans votre dossier lib / sous un fichier appelé exceptions.rb, qui ressemblerait à ceci:

module Exceptions
  class AuthenticationError < StandardError; end
  class InvalidUsername < AuthenticationError; end
end

et vous l'utiliseriez comme ceci:

raise Exceptions::InvalidUsername
Mike Lewis
la source
Pour les gemmes, vous devrez peut-être également inclure le fichier d'exception. Voir cet exemple, encore une fois de httparty: github.com/jnunemaker/httparty/blob
Jason Swett
37
Pourquoi les mettre en espace dans le Exceptionsmodule?
ABMagil
13
Je pense que /libce n'est peut-être pas le lieu des erreurs. Ils sont très spécifiques aux applications et j'ai l'impression que le code que je mets /libest censé être du code qui pourrait être réutilisé dans d'autres applications.
wuliwong
1
Les instructions de Ruby on Rails n'ont pas fonctionné pour moi - une étape supplémentaire est-elle nécessaire pour charger réellement ce nouveau fichier dans le cas typique?
Meekohi le
1
@ABMagil semble devoir le faire, sinon Unable to autoload constant Exceptions, expected /app/lib/exceptions.rb to define itl'autre option serait une classe par exception, je pense
ryan2johnson9
25

Je pense que pour avoir des fichiers source cohérents dans votre projet, vous devez définir des erreurs dans la classe dans laquelle peuvent les jeter et nulle part ailleurs.

Une certaine hiérarchie peut être utile - les espaces de noms sont bons pour garder les chaînes redondantes hors des noms de types - mais c'est plus une question de goût - il n'est pas nécessaire d'aller trop loin à condition que vous ayez au moins un type d'exception personnalisé dans votre application que vous utilisez pour différencier entre les cas d'exception «intentionnels» et «accidentels».

Dean Radcliffe
la source
8
Alors qu'en théorie vous avez raison, que se passe-t-il lorsque la même erreur peut être soulevée par différentes classes dans des situations totalement différentes?
Alain
1
@Alain Pourquoi ne pas définir les erreurs utilisées par plus d'une classe dans un module Exceptions / Erreurs, mais laisser toutes les autres définies dans la classe unique qui les utilise?
Scott W
@ScottW, Dans ce cas, nous comptons sur le développeur pour ne pas oublier de vérifier.
Josh Saint Jacque
22

dans les rails, vous pouvez créer un app/errorsrépertoire

# app/errors/foo_error.rb
class FooError < StandardError; end

redémarrez spring / server et il devrait le récupérer

schpet
la source
Comment dois-je signaler ces exceptions?
Nikhil Wagh le
@NikhilWagh soit raise FooError, "Example message..."ouraise FooError.new("Example message...")
schpet
13

C'est une vieille question, mais je voulais partager comment je gère les erreurs personnalisées dans Rails, y compris l'attachement de messages d'erreur, les tests et comment gérer cela avec des ActiveRecordmodèles.

Création d'une erreur personnalisée

class MyClass
  # create a custome error
  class MissingRequirement < StandardError; end

  def my_instance_method
    raise MyClass::MissingRequirement, "My error msg" unless true   
  end
end

Test (minitest)

test "should raise MissingRequirement if ____ is missing"
  # should raise an error
  error = assert_raises(MyClass::MissingRequirement) {
    MyClass.new.my_instance_method
  }

  assert error.message = "My error msg"
end

Avec ActiveRecord

Je pense qu'il vaut la peine de noter que si vous travaillez avec un ActiveRecordmodèle, un modèle populaire consiste à ajouter une erreur au modèle comme décrit ci-dessous, afin que vos validations échouent:

def MyModel < ActiveRecord::Base
  validate :code_does_not_contain_hyphens

  def code_does_not_contain_hyphens
    errors.add(:code, "cannot contain hyphens") if code.include?("-")
  end
end

Lorsque les validations sont exécutées, cette méthode se greffera sur la ActiveRecord::RecordInvalidclasse d'erreur d'ActiveRecord et entraînera l'échec des validations.

J'espère que cela t'aides!

Mat
la source
9

Pour vous assurer que le chargement automatique fonctionne comme prévu dans Rails 4.1.10 pour plusieurs classes d'erreur personnalisées, vous devrez spécifier des fichiers séparés pour chacune. Cela devrait fonctionner en développement avec son rechargement dynamique.

Voici comment je configure les erreurs dans un projet récent:

Dans lib/app_name/error/base.rb

module AppName
    module Error
        class Base < StandardError; end
    end
end

et dans les erreurs personnalisées ultérieures, comme dans lib/app_name/error/bad_stuff.rb

module AppName
    module Error
        class BadStuff < ::AppName::Error::Base; end
    end
end

Vous devriez alors pouvoir appeler vos erreurs via:

 raise AppName::Error::BadStuff.new("Bad stuff just happened")
spyle
la source
Et si vous ne voulez pas un fichier séparé pour chaque nouvelle erreur, mettez-les tous danslib/app_name/error.rb
jlhonora
Obtenir un uninitialized constant MyController::AppName. J'appelle augmenter dans mon contrôleur
Nikhil Wagh