Управление порядком проверки рельсов
У меня есть модель рельсов, которая имеет 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" всегда запускается до того, как метод "проверяет". Это означает, что если пользователь оставляет одно из обязательных полей пустым, рельсы вызывают ошибку, когда он пытается выполнить вычисление с пустым атрибутом.
Итак, как я могу сначала проверить наличие всех необходимых атрибутов?
Ответы
Ответ 1
Я не уверен, что он гарантирует, в каком порядке эти проверки будут запущены, поскольку это может зависеть от того, как сам хеш attributes
заканчивается. Возможно, вам лучше сделать ваш метод validate
более устойчивым и просто не работать, если отсутствуют некоторые из необходимых данных. Например:
def within_required_range?
return if ([ a, b, c, d ].find(&:blank?))
# ...
end
Это выйдет из строя, если какая-либо из переменных a
через d
пуста, включая нулевые, пустые массивы или строки и т.д.
Ответ 2
Альтернативой для более сложных ситуаций было бы создать вспомогательный метод, который сначала выполняет проверки для зависимых атрибутов. Тогда вы можете сделать свой: calculate_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
Мне нужно было создать что-то подобное для проекта, потому что проверки зависимых атрибутов были довольно сложными. Мой эквивалент: calculate_ok? будет генерировать исключение, если зависимые атрибуты не были правильно проверены.
Преимущества:
- относительно СУХОЙ, особенно если ваши проверки сложны.
- гарантирует, что ваш массив ошибок сообщает о правильной неудачной проверке вместо макро-проверки
- автоматически включает любые дополнительные проверки зависимых атрибутов, которые вы добавляете позже
Предостережения:
- потенциально выполняет все проверки дважды
- вам может не потребоваться выполнение всех проверок на зависимых атрибутах
Ответ 3
Отъезд http://railscasts.com/episodes/211-validations-in-rails-3
После реализации пользовательского валидатора вы просто выполните
validates :attribute1, :calculations_ok => true
Это должно решить вашу проблему.
Ответ 4
Решение James H имеет для меня наибольший смысл. Однако, еще одна вещь, которую следует учитывать, заключается в том, что если у вас есть условия для зависимых проверок, их нужно также проверить для параметра depend_attributes_valid? вызов на работу.
т.
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