1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
|
module Validation
module Rules
# A hash of rules for this object
def rules
@rules ||= {}
end
# A hash of errors for this object
def errors
@errors ||= {}
end
# Define a rule for this object
#
# The rule parameter can be one of the following:
#
# * a symbol that matches to a class in the Validation::Rule namespace
# * e.g. rule(:field, :not_empty)
# * a hash containing the rule as the key and it's parameters as the values
# * e.g. rule(:field, :length => {:minimum => 3, :maximum => 5})
# * an array combining the two previous types
def rule(field, rule)
field = field.to_sym
if rules[field].nil?
rules[field] = []
end
begin
if rule.respond_to?(:each_pair)
add_parameterized_rule(field, rule)
elsif rule.respond_to?(:each)
rule.each do |r|
if r.respond_to?(:each_pair)
add_parameterized_rule(field, r)
else
r = Validation::Rule.const_get(camelize(r)).new
add_object_to_rule(r)
rules[field] << r
end
end
else
rule = Validation::Rule.const_get(camelize(rule)).new
add_object_to_rule(rule)
rules[field] << rule
end
rescue NameError => e
raise InvalidRule.new(e)
end
self
end
# Determines if this object is valid. When a rule fails for a field,
# this will stop processing further rules. In this way, you'll only get
# one error per field
def valid?
valid = true
rules.each_pair do |field, rules|
if ! @obj.respond_to?(field)
raise InvalidKey
end
rules.each do |r|
if ! r.valid_value?(@obj.send(field))
valid = false
errors[field] = {:rule => r.error_key, :params => r.params}
break
end
end
end
@valid = valid
end
protected
# Adds a parameterized rule to this object
def add_parameterized_rule(field, rule)
rule.each_pair do |key, value|
r = Validation::Rule.const_get(camelize(key)).new(value)
add_object_to_rule(r)
rules[field] << r
end
end
# Adds this validation object to a rule if it can accept it
def add_object_to_rule(rule)
if rule.respond_to?(:obj=)
rule.obj = @obj
end
end
# Converts a symbol to a class name, taken from rails
def camelize(term)
string = term.to_s
string = string.sub(/^[a-z\d]*/) { $&.capitalize }
string.gsub(/(?:_|(\/))([a-z\d]*)/i) { $2.capitalize }.gsub('/', '::')
end
end
# Validator is a simple ruby validation class. You don't use it directly
# inside your classes like just about every other ruby validation class out
# there. I chose to implement it in this way so I didn't automatically
# pollute the namespace of the objects I wanted to validate.
#
# This also solves the problem of validating forms very nicely. Frequently
# you will have a form that represents many different data objects in your
# system, and you can pre-validate everything before doing any saving.
class Validator
include Validation::Rules
def initialize(obj)
@rules = self.class.rules if self.class.is_a?(Validation::Rules)
@obj = obj
end
end
# InvalidKey is raised if a rule is added to a field that doesn't exist
class InvalidKey < RuntimeError
end
# InvalidRule is raised if a rule is added that doesn't exist
class InvalidRule < RuntimeError
end
end
|