控制 Rails 验证的顺序

发布于 2024-11-07 01:51:44 字数 679 浏览 1 评论 0原文

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

那么我怎样才能首先检查所有必需的属性是否存在呢?

I have a rails model which has 7 numeric attributes filled in by the user via a form.

I need to validate the presence of each of these attributes which is obviously easy using

validates :attribute1, :presence => true
validates :attribute2, :presence => true
# and so on through the attributes

However I also need to run a custom validator which takes a number of the attributes and does some calculations with them. If the result of these calculations is not within a certain range then the model should be declared invalid.

On it's own, this too is easy

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

However the problem is that the method "validate" always gets run before the method "validates". This means that if the user leaves one of the required fields blank, rails throws an error when it tries to do a calculation with a blank attribute.

So how can I check the presence of all the required attributes first?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

╰沐子 2024-11-14 01:51:44

我不确定是否能保证这些验证按什么顺序运行,因为它可能取决于 attributes 哈希本身的最终排序方式。您可能最好让您的 validate 方法更具弹性,并且在缺少某些所需数据时根本不运行。例如:

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

  # ...
end

如果变量 ad 中的任何一个变量为空(包括 nil、空数组或字符串等),则会退出。

I'm not sure it's guaranteed what order these validations get run in, as it might depend on how the attributes hash itself ends up ordered. You may be better off making your validate method more resilient and simply not run if some of the required data is missing. For example:

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

  # ...
end

This will bail out if any of the variables a through d are blank, which includes nil, empty arrays or strings, and so forth.

与往事干杯 2024-11-14 01:51:44

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

优点:

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

注意事项:

  • 所有验证两次
  • 可能会运行您可能不希望的 对依赖属性运行的所有验证

An alternative for slightly more complex situations would be to create a helper method which runs the validations for the dependent attributes first. Then you can make your :calculations_ok? validation run conditionally.

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

I had to create something like this for a project because the validations on the dependent attributes were quite complex. My equivalent of :calculations_ok? would throw an exception if the dependent attributes didn't validate properly.

Advantages:

  • relatively DRY, especially if your validations are complex
  • ensures that your errors array reports the right failed validation instead of the macro-validation
  • automatically includes any additional validations on the dependent attributes you add later

Caveats:

  • potentially runs all validations twice
  • you may not want all validations to run on the dependent attributes
玩物 2024-11-14 01:51:44

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

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

validates :attribute1, :calculations_ok => true

这样做即可解决您的问题。

Check out http://railscasts.com/episodes/211-validations-in-rails-3

After implementing a custom validator, you'll simply do

validates :attribute1, :calculations_ok => true

That should solve your problem.

╰ゝ天使的微笑 2024-11-14 01:51:44

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

我认为 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

I recall running into this issue quite some time ago, still unclear if validations order can be set and execution chain halted if a validation returns error.

I don't think Rails offers this option. It makes sense; we want to show all of the errors on the record (including those that come after a failing, due to invalid input, validation).

One possible approach is to validate only if the input to validate is present:

def within_required_range?
  return unless [attribute1, attribute2, ..].all?(&:present?)
  
  # check the calculations and return true or false here
end

Make it pretty & better structured (single responsibility) with Rails idiomatic validation options:

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
清音悠歌 2024-11-14 01:51:44

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

The James H solution makes the most sense to me. One extra thing to consider however, is that if you have conditions on the dependent validations, they need to be checked also in order for the dependent_attributes_valid? call to work.

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文