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