我有一个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 在尝试使用空白属性进行计算时会引发错误。
那么如何首先检查所有必需属性的存在呢?
我不确定是否可以保证这些验证的运行顺序,因为它可能取决于attributes
哈希本身的最终顺序。最好使validate
方法更具弹性,并且在缺少某些所需数据时不要运行。例如:
def within_required_range?
return if ([ a, b, c, d ].any?(&:blank?))
# ...
end
如果通过d
a
的任何变量为空,包括 nil、空数组或字符串等,这将得到救助。
对于稍微复杂的情况,另一种方法是创建一个帮助程序方法,该方法首先运行依赖属性的验证。然后你可以让你的:calculations_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
我必须为项目创建这样的东西,因为对依赖属性的验证非常复杂。我的等价物:calculations_ok?如果依赖属性未正确验证,将引发异常。
优势:
- 相对干燥,特别是如果您的验证很复杂
- 确保错误数组报告正确的失败验证,而不是宏验证
- 自动包含对稍后添加的依赖属性的任何其他验证
警告:
- 可能运行所有验证两次
- 您可能不希望所有验证都对依赖属性运行
查看 http://railscasts.com/episodes/211-validations-in-rails-3
实现自定义验证器后,您只需执行
validates :attribute1, :calculations_ok => true
这应该可以解决您的问题。
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
我记得很久以前遇到过这个问题,仍然不清楚是否可以设置验证顺序并且如果验证返回错误,执行链是否停止。
我不认为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