控制轨道验证的顺序



我有一个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

相关内容

  • 没有找到相关文章

最新更新