Controlling the order of rails validations

前端 未结 5 548
天命终不由人
天命终不由人 2020-12-02 22:05

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

相关标签:
5条回答
  • 2020-12-02 22:39

    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.

    0 讨论(0)
  • 2020-12-02 22:46

    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.

    0 讨论(0)
  • 2020-12-02 22:51

    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
    0 讨论(0)
  • 2020-12-02 22:56

    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 (incl. 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
    
    0 讨论(0)
  • 2020-12-02 23:00

    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
    
    0 讨论(0)
提交回复
热议问题