web-dev-qa-db-fra.com

Contrôler l'ordre de Rails validations

J'ai un Rails modèle comportant 7 attributs numériques remplis par l'utilisateur via un formulaire.

J'ai besoin de valider la présence de chacun de ces attributs qui est évidemment facile à utiliser

validates :attribute1, :presence => true
validates :attribute2, :presence => true
# and so on through the attributes

Cependant, j'ai aussi besoin d'exécuter un validateur personnalisé qui prend un certain nombre d'attributs et fait quelques calculs avec eux. Si le résultat de ces calculs n'est pas dans une certaine plage, le modèle doit être déclaré invalide.

Sur c'est propre, c'est aussi facile

validate :calculations_ok?

def calculations_ok?
  errors[:base] << "Not within required range" unless within_required_range?
end

def within_required_range?
  # check the calculations and return true or false here
end

Cependant, le problème est que la méthode "VALIDATE" est toujours exécutée avant que la méthode "Validate". Cela signifie que si l'utilisateur quitte l'un des champs obligatoires en blanc, Rails jette une erreur lorsqu'il essaie de procéder à un calcul avec un attribut vierge.

Alors, comment puis-je vérifier la présence de tous les attributs requis en premier?

50
David Tuite

Je ne suis pas sûr qu'il soit garanti à quel ordre ces validations sont exécutées, car cela pourrait dépendre de la manière dont le hachage attributes se termine par ordre. Vous risquez peut-être mieux de faire votre validate méthode plus résiliente et tout simplement pas exécuté si certaines des données requises sont manquantes. Par exemple:

def within_required_range?
  return if ([ a, b, c, d ].find(&:blank?))

  # ...
end

Cela fera caution si l'une des variables a via d sont vides, qui comprend NIL, des tableaux vides ou des cordes, etc.

19
tadman

Une alternative pour des situations légèrement plus complexes serait de créer une méthode d'assistance qui exécute d'abord les validations des attributs dépendants. Ensuite, vous pouvez faire de vos: calculs_ok? la validation fonctionne conditionnellement.

validates :attribute1, :presence => true
validates :attribute2, :presence => true
...
validates :attribute7, :presence => true

validate :calculations_ok?, :unless => Proc.new { |a| a.dependent_attributes_valid? }

def dependent_attributes_valid?
  [:attribute1, ..., :attribute7].each do |field|
    self.class.validators_on(field).each { |v| v.validate(self) }
    return false if self.errors.messages[field].present?
  end
  return true
end

Je devais créer quelque chose comme celui-ci pour un projet car les validations des attributs dépendants étaient assez complexes. Mon équivalent de: calculs_ok? Jeterais une exception si les attributs dépendants ne validaient pas correctement.

Avantages:

  • relativement sec, surtout si vos validations sont complexes
  • veille à ce que votre réseau d'erreurs rapporte la bonne validation échouée au lieu de la macro-validation
  • inclut automatiquement toutes les validations supplémentaires sur les attributs dépendants que vous ajoutez plus tard

CAVEZIERS:

  • potentiellement exécute toutes les validations deux fois
  • vous ne voulez peut-être pas que toutes les validations fonctionnent sur les attributs dépendants
9
James H

Vérifiez http://railcastss.com/episodes/211-validations-in-rails-

Après avoir implémenté un validateur personnalisé, vous ferez simplement

validates :attribute1, :calculations_ok => true

Cela devrait résoudre votre problème.

2
David Sulc

La solution James H offre le plus de sens pour moi. Une chose supplémentaire à considérer cependant, c'est que si vous avez des conditions sur les validations à charge, elles doivent également être vérifiées pour le dépendant_attributes_valid? appeler au travail.

c'est à dire.

    validates :attribute1, presence: true
    validates :attribute1, uniqueness: true, if: :attribute1?
    validates :attribute1, numericality: true, unless: Proc.new {|r| r.attribute1.index("@") }
    validates :attribute2, presence: true
    ...
    validates :attribute7, presence: true

    validate :calculations_ok?, unless: Proc.new { |a| a.dependent_attributes_valid? }

    def dependent_attributes_valid?
      [:attribute1, ..., :attribute7].each do |field|
        self.class.validators_on(field).each do |v|
          # Surely there is a better way with rails?
          existing_error = v.attributes.select{|a| self.errors[a].present? }.present?

          if_condition = v.options[:if]
          validation_if_condition_passes = if_condition.blank?
          validation_if_condition_passes ||= if_condition.class == Proc ? if_condition.call(self) : !!self.send(if_condition)

          unless_condition = v.options[:unless]
          validation_unless_condition_passes = unless_condition.blank?
          validation_unless_condition_passes ||= unless_condition.class == Proc ? unless_condition.call(self) : !!self.send(unless_condition)

          if !existing_error and validation_if_condition_passes and validation_unless_condition_passes
            v.validate(self)
          end
        end
        return false if self.errors.messages[field].present?
      end
      return true
    end
1
Jason Kotchoff