custom validations only if all attributes are valid

I can see some advantge of declaring a validation dependency. Checking
if an age is less than 16 makes no sense if the age has failed a
validation on being a number already.

maybe something like:

class User < AR::Base
validates_numericality_of :age

#custom validation
validate :user_older_16, :depends => :age

def user_older_16
errors.add(:age, "should be older 16") if age <= 16
end
end

Where :user_older_16 would only run if age has no errors. In the
context shown above this is a sensible dependency, but used
incorrectly the concerns expressed by others would be valid.

Another suggestion:

class User < AR::Base

   validates_with_dependency do
validates_numericality_of :age
     validate :user_older_16
   end

def user_older_16
errors.add(:age, "should be older 16") if age <= 16
end
end

In the above the validations in the validate_with_dependency block
would execute in order, exiting the sequence if any fail.

I think the suggestion to have dependent validaiton is a good one as
it allows for structuring simiple validations where you know a
validation cannot possibly pass if an earlier one has failed.

Cheers,

Anthony Richardson.

Yeah, I couldn't understand what Ken meant by his "state-machine" approach.

I can't think of a dry way of currently doing that.

I'm just not much concerned about this as I don't use AR and Sequel's way of doing validation is by defining a "validate" method, so I'd put my logic there, but anyway...

A poor choice of words maybe, I was trying to say that at some point in time, I have had models that needed a hierarchy of potential errors and that some errors are not appropriate to show at certain times. A object that goes thru states is a good example.

- Ken

Anthony, good point!

validates_with_dependency is the a specific case of suggested ‘validate_further’ approach.

Two stage validation doesn’t require you to manage dependencies (order, etc.) so no need to specify them.

You just rely on results from first stage.

One more example from real project:

order.rb

validates_presence_of :shop
validates_presence_of :line_items
validates_associated :billing_address

validate_further :on => :create do
errors.add(:signature, :invalid) unless signature_verified?
end

method signature_verified? calculates signature with

  • shop.secret_key - shop should present
  • amount, quantity, name for each line_item
  • attributes for billing/shipping, etc.
    and compares it with received value from user

do I really have to check manually that everything valid before calculate signature?

If you only want to run a validation on an attribute if it is valid at that point in the validation process then here's a trick:

module AttributeValidation
  extend ActiveSupport::Concern

  included do
    attribute_method_suffix '_valid?', '_invalid?'
  end

  private

  def attribute_valid?(attr)
    !attribute_invalid?(attr)
  end

  def attribute_invalid?(attr)
    errors.include?(attr.to_sym)
  end
end

include this module in AR::Base or specific models - it's up to you. Once included you can then set up your validations like this:

class User < ActiveRecord::Base
  include AttributeValidation

  attr_accessible :name, :age

  validates :name, presence: true, length: { maximum: 100 }
  validates :age, presence: true, numericality: { only_integer: true }
  validate :over_minimum_age, if: :age_valid?

  def over_minimum_age
    errors.add(:age, :invalid) if age < 16
  end
end

this will only run the minimum age validation once it has passed the presence and numericality checks. One advantage of doing this is you don't need guard checks when comparing the age (normally you'd get a "NoMethodError: undefined method `<' for nil:NilClass")

Andrew