我有一个rails模型,其中有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
但问题是“验证”方法总是在方法“验证”之前运行。这意味着如果用户将其中一个必填字段留空,则rails尝试使用空白属性进行计算时会抛出错误。
那么如何首先检查所有必需属性的存在?
答案 0 :(得分:18)
我不确定这些验证的运行顺序是什么,因为它可能取决于attributes
哈希本身最终如何排序。您可能最好使validate
方法更具弹性,如果缺少某些所需数据,则无法运行。例如:
def within_required_range?
return if ([ a, b, c, d ].find(&:blank?))
# ...
end
如果a
到d
中的任何变量都是空白,包括nil,空数组或字符串等,这将会挽救。
答案 1 :(得分:9)
稍微复杂一些情况的替代方法是创建一个辅助方法,该方法首先运行依赖属性的验证。然后你可以做你的: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?如果依赖属性未正确验证,则会抛出异常。
优点:
注意事项:
答案 2 :(得分:2)
查看http://railscasts.com/episodes/211-validations-in-rails-3
实施自定义验证器后,您只需执行
validates :attribute1, :calculations_ok => true
这应该可以解决你的问题。
答案 3 :(得分:1)
James H 解决方案对我来说最有意义。但是要考虑的另一件事是,如果您对依赖验证有条件,还需要检查它们以便依赖于dependent_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
答案 4 :(得分:0)
我记得很早以前就遇到了这个问题,仍然不清楚是否可以设置验证顺序,并且如果验证返回错误,执行链也将暂停。
我不认为Rails提供此选项。这说得通;我们希望在记录中显示所有错误(包括由于无效输入,验证而导致失败的错误)。
一种可能的方法是仅在存在要验证的输入时进行验证:
def within_required_range?
return unless [:attribute1, attribute2, ..].all?(&:present?)
# check the calculations and return true or false here
end
使用Rails惯用的验证选项,使其更漂亮且结构更好(单一职责):
validates :attribute1, :presence => true
validates :attribute2, :presence => true
# and so on through the attributes
validate :calculations_ok?, if: :attributes_present?
private
def attributes_present?
[:attribute1, attribute2, ..].all?(&:present?)
end
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