控制导轨验证的顺序

时间:2011-05-11 14:38:18

标签: ruby-on-rails ruby-on-rails-3 validation activemodel

我有一个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尝试使用空白属性进行计算时会抛出错误。

那么如何首先检查所有必需属性的存在?

5 个答案:

答案 0 :(得分:18)

我不确定这些验证的运行顺序是什么,因为它可能取决于attributes哈希本身最终如何排序。您可能最好使validate方法更具弹性,如果缺少某些所需数据,则无法运行。例如:

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

  # ...
end

如果ad中的任何变量都是空白,包括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