51

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

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

4

5 回答 5

21

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

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

  # ...
end

a如果通过的任何变量为空,这将退出d,其中包括 nil、空数组或字符串等。

于 2011-05-11T14:55:07.997 回答
9

对于稍微复杂的情况,另一种方法是创建一个辅助方法,该方法首先运行相关属性的验证。然后你可以让你的: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? 如果相关属性未正确验证,则会引发异常。

优点:

  • 相对干燥,特别是如果您的验证很复杂
  • 确保您的错误数组报告正确的失败验证而不是宏验证
  • 自动包含对您稍后添加的依赖属性的任何其他验证

注意事项:

  • 可能会运行所有验证两次
  • 您可能不希望所有验证都在依赖属性上运行
于 2012-07-16T20:47:59.580 回答
2

查看http://railscasts.com/episodes/211-validations-in-rails-3

实现自定义验证器后,您只需执行

validates :attribute1, :calculations_ok => true

那应该可以解决您的问题。

于 2011-05-11T14:50:43.040 回答
1

James H解决方案对我来说最有意义。但是,要考虑的另一件事是,如果您对相关验证有条件,则还需要检查它们以获取dependent_attributes_valid?打电话上班。

IE。

    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
于 2016-04-03T23:49:17.570 回答
1

我记得很久以前遇到过这个问题,如果验证返回错误,是否可以设置验证顺序并停止执行链仍然不清楚。

我不认为 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
于 2020-03-24T15:59:42.490 回答