Controlling the order of rails validations

两盒软妹~` 提交于 2019-11-27 12:33:29

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 ].find(&: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.

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

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.

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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!