Контроль порядка валидаций рельсов - PullRequest
48 голосов
/ 11 мая 2011

У меня есть модель рельсов, которая имеет 7 числовых атрибутов, заполненных пользователем через форму.

Мне нужно проверить наличие каждого из этих атрибутов, что, очевидно, легко с помощью

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

Однако мне также нужно запустить специальный валидатор, который принимает ряд атрибутов и выполняет с ними некоторые вычисления.Если результат этих вычислений не находится в определенном диапазоне, тогда модель должна быть объявлена ​​недействительной.

Само по себе это тоже легко

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

Однако проблема заключается в том, что метод«validate» всегда запускается до того, как метод «validate».Это означает, что если пользователь оставляет одно из обязательных полей пустым, rails выдает ошибку, когда пытается выполнить вычисление с пустым атрибутом.

Так как же сначала проверить наличие всех необходимых атрибутов?

Ответы [ 4 ]

19 голосов
/ 11 мая 2011

Я не уверен, что гарантированно, в каком порядке запускаются эти проверки, так как это может зависеть от того, как сам хеш attributes будет упорядочен.Возможно, вам лучше сделать метод validate более устойчивым и просто не запускаться, если отсутствуют некоторые необходимые данные.Например:

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

  # ...
end

Сработает, если любая из переменных от a до d не заполнена, включая ноль, пустые массивы или строки и т. Д.

9 голосов
/ 17 июля 2012

Альтернативой для немного более сложных ситуаций может быть создание вспомогательного метода, который сначала запускает проверки для зависимых атрибутов.Тогда вы можете сделать свой: computing_ok?условный запуск проверки.

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

Мне пришлось создать что-то подобное для проекта, поскольку проверки зависимых атрибутов были довольно сложными.Мой эквивалент: computing_ok?выдаст исключение, если зависимые атрибуты не будут правильно проверены.

Преимущества:

  • относительно СУХОЙ, особенно если ваши проверки сложные
  • гарантирует, что ваши ошибкимассив сообщает о правильной неудачной проверке вместо макро-проверки
  • автоматически включает любые дополнительные проверки для зависимых атрибутов, которые вы добавляете позже

Предупреждения:

  • потенциально запускает все проверки дважды
  • , возможно, вы не захотите запускать все проверки для зависимых атрибутов
2 голосов
/ 11 мая 2011

Оформить http://railscasts.com/episodes/211-validations-in-rails-3

После реализации пользовательского валидатора вы просто выполните

validates :attribute1, :calculations_ok => true

Это должно решить вашу проблему.

1 голос
/ 04 апреля 2016

Решение Джеймс Х имеет для меня наибольшее значение. Однако следует учесть еще одну вещь: если у вас есть условия для зависимых проверок, их также нужно проверить, чтобы получить для зависимых_каталогов_данных? призыв к работе.

т.

    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
...