lgustafson-validatable 1.8.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/README.rdoc +116 -0
  2. data/Rakefile +34 -0
  3. data/VERSION.yml +5 -0
  4. data/lib/validatable/child_validation.rb +15 -0
  5. data/lib/validatable/errors.rb +106 -0
  6. data/lib/validatable/included_validation.rb +9 -0
  7. data/lib/validatable/macros.rb +314 -0
  8. data/lib/validatable/object_extension.rb +40 -0
  9. data/lib/validatable/requireable.rb +26 -0
  10. data/lib/validatable/understandable.rb +31 -0
  11. data/lib/validatable/validatable_class_methods.rb +87 -0
  12. data/lib/validatable/validatable_instance_methods.rb +106 -0
  13. data/lib/validatable/validations/validates_acceptance_of.rb +14 -0
  14. data/lib/validatable/validations/validates_associated.rb +13 -0
  15. data/lib/validatable/validations/validates_confirmation_of.rb +23 -0
  16. data/lib/validatable/validations/validates_each.rb +14 -0
  17. data/lib/validatable/validations/validates_exclusion_of.rb +17 -0
  18. data/lib/validatable/validations/validates_format_of.rb +16 -0
  19. data/lib/validatable/validations/validates_inclusion_of.rb +17 -0
  20. data/lib/validatable/validations/validates_length_of.rb +30 -0
  21. data/lib/validatable/validations/validates_numericality_of.rb +27 -0
  22. data/lib/validatable/validations/validates_presence_of.rb +17 -0
  23. data/lib/validatable/validations/validates_true_for.rb +13 -0
  24. data/lib/validatable/validations/validation_base.rb +91 -0
  25. data/lib/validatable.rb +36 -0
  26. data/test/functional/test_validatable.rb +519 -0
  27. data/test/functional/test_validates_acceptance_of.rb +16 -0
  28. data/test/functional/test_validates_associated.rb +41 -0
  29. data/test/functional/test_validates_confirmation_of.rb +56 -0
  30. data/test/functional/test_validates_each.rb +14 -0
  31. data/test/functional/test_validates_exclusion_of.rb +29 -0
  32. data/test/functional/test_validates_format_of.rb +34 -0
  33. data/test/functional/test_validates_inclusion_of.rb +29 -0
  34. data/test/functional/test_validates_length_of.rb +64 -0
  35. data/test/functional/test_validates_numericality_of.rb +57 -0
  36. data/test/functional/test_validates_presence_of.rb +16 -0
  37. data/test/functional/test_validates_true_for.rb +27 -0
  38. data/test/test_helper.rb +26 -0
  39. data/test/unit/test_errors.rb +88 -0
  40. data/test/unit/test_understandable.rb +19 -0
  41. data/test/unit/test_validatable.rb +38 -0
  42. data/test/unit/test_validates_acceptance_of.rb +45 -0
  43. data/test/unit/test_validates_associated.rb +29 -0
  44. data/test/unit/test_validates_confirmation_of.rb +76 -0
  45. data/test/unit/test_validates_exclusion_of.rb +23 -0
  46. data/test/unit/test_validates_format_of.rb +44 -0
  47. data/test/unit/test_validates_inclusion_of.rb +23 -0
  48. data/test/unit/test_validates_length_of.rb +80 -0
  49. data/test/unit/test_validates_numericality_of.rb +82 -0
  50. data/test/unit/test_validates_presence_of.rb +35 -0
  51. data/test/unit/test_validates_true_for.rb +29 -0
  52. data/test/unit/test_validation_base.rb +52 -0
  53. metadata +164 -0
@@ -0,0 +1,31 @@
1
+ module Validatable
2
+ module Understandable #:nodoc:
3
+ module ClassMethods #:nodoc:
4
+ def understands(*args)
5
+ understandings.concat args
6
+ end
7
+
8
+ def understandings
9
+ @understandings ||= []
10
+ end
11
+
12
+ def all_understandings
13
+ return understandings + self.superclass.all_understandings if self.superclass.respond_to? :all_understandings
14
+ understandings
15
+ end
16
+ end
17
+
18
+ def self.included(klass)
19
+ klass.extend ClassMethods
20
+ end
21
+
22
+ def must_understand(hash)
23
+ invalid_options = hash.inject([]) do |errors, (key, value)|
24
+ errors << key.to_s unless self.class.all_understandings.include?(key)
25
+ errors
26
+ end
27
+ raise ArgumentError.new("invalid options: #{invalid_options.join(', ')}") if invalid_options.any?
28
+ true
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,87 @@
1
+ module Validatable
2
+ module ClassMethods #:nodoc:
3
+
4
+ def validate_children(instance, group)
5
+ self.children_to_validate.each do |child_validation|
6
+ next unless child_validation.should_validate?(instance)
7
+ child_or_children = instance.send child_validation.attribute
8
+ [child_or_children].flatten.each do |child|
9
+ if (child.respond_to?(:valid_for_group?))
10
+ child.valid_for_group?(group)
11
+ else
12
+ child.valid?
13
+ end
14
+ child.errors.each do |attribute, messages|
15
+ if messages.is_a?(String)
16
+ add_error(instance, child_validation.map[attribute.to_sym] || attribute, messages)
17
+ else
18
+ messages.each do |message|
19
+ add_error(instance, child_validation.map[attribute.to_sym] || attribute, message)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ def all_before_validations
28
+ if self.superclass.respond_to? :all_before_validations
29
+ return before_validations + self.superclass.all_before_validations
30
+ end
31
+ before_validations
32
+ end
33
+
34
+ def before_validations
35
+ @before_validations ||= []
36
+ end
37
+
38
+ def all_validations
39
+ if self.respond_to?(:superclass) && self.superclass.respond_to?(:all_validations)
40
+ return validations + self.superclass.all_validations
41
+ end
42
+ validations
43
+ end
44
+
45
+ def validations
46
+ @validations ||= []
47
+ end
48
+
49
+ def add_error(instance, attribute, msg)
50
+ instance.errors.add(attribute, msg)
51
+ end
52
+
53
+ def validation_keys_include?(key)
54
+ validations.map { |validation| validation.key }.include?(key)
55
+ end
56
+
57
+ def validations_to_include
58
+ @validations_to_include ||= []
59
+ end
60
+
61
+ protected
62
+
63
+ def add_validations(args, klass)
64
+ options = args.last.is_a?(Hash) ? args.pop : {}
65
+ args.each do |attribute|
66
+ new_validation = klass.new self, attribute, options
67
+ self.validations << new_validation
68
+ self.create_valid_method_for_groups new_validation.groups
69
+ end
70
+ end
71
+
72
+ def create_valid_method_for_groups(groups)
73
+ groups.each do |group|
74
+ self.class_eval do
75
+ define_method "valid_for_#{group}?".to_sym do
76
+ valid_for_group?(group)
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ def children_to_validate
83
+ @children_to_validate ||= []
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,106 @@
1
+ module Validatable
2
+ def self.included(klass) #:nodoc:
3
+ klass.extend Validatable::ClassMethods
4
+ klass.extend Validatable::Macros
5
+ end
6
+
7
+ # call-seq: valid?
8
+ #
9
+ # Returns true if no errors were added otherwise false. Only executes validations that have no :groups option specified
10
+ def valid?
11
+ valid_for_group?(nil)
12
+ end
13
+
14
+ # call-seq: errors
15
+ #
16
+ # Returns the Errors object that holds all information about attribute error messages.
17
+ def errors
18
+ @errors ||= Validatable::Errors.new
19
+ end
20
+
21
+ def valid_for_group?(group) #:nodoc:
22
+ errors.clear
23
+ run_before_validations
24
+ self.class.validate_children(self, group)
25
+ self.validate_group(group)
26
+ errors.empty?
27
+ end
28
+
29
+ def times_validated(key) #:nodoc:
30
+ times_validated_hash[key] || 0
31
+ end
32
+
33
+ def increment_times_validated_for(validation) #:nodoc:
34
+ if validation.key != nil
35
+ if times_validated_hash[validation.key].nil?
36
+ times_validated_hash[validation.key] = 1
37
+ else
38
+ times_validated_hash[validation.key] += 1
39
+ end
40
+ end
41
+ end
42
+
43
+ # call-seq: validate_only(key)
44
+ #
45
+ # Only executes a specified validation. The argument should follow a pattern based on the key of the validation.
46
+ # Examples:
47
+ # * validates_presence_of :name can be run with obj.validate_only("presence_of/name")
48
+ # * validates_presence_of :birthday, :key => "a key" can be run with obj.validate_only("presence_of/a key")
49
+ def validate_only(key)
50
+ validation_name, attribute_name = key.split("/")
51
+ validation_name = validation_name.split("_").collect{|word| word.capitalize}.join
52
+ validation_key = "#{self.class.name}/Validatable::Validates#{validation_name}/#{attribute_name}"
53
+ validation = self.class.all_validations.find { |validation| validation.key == validation_key }
54
+ raise ArgumentError.new("validation with key #{validation_key} could not be found") if validation.nil?
55
+ errors.clear
56
+ run_validation(validation)
57
+ end
58
+
59
+ protected
60
+ def times_validated_hash #:nodoc:
61
+ @times_validated_hash ||= {}
62
+ end
63
+
64
+ def validate_group(group) #:nodoc:
65
+ validation_levels.each do |level|
66
+ validations_for_level_and_group(level, group).each do |validation|
67
+ run_validation(validation) if validation.should_validate?(self)
68
+ end
69
+ return unless self.errors.empty?
70
+ end
71
+ end
72
+
73
+ def run_validation(validation) #:nodoc:
74
+ validation_result = validation.valid?(self)
75
+ add_error(validation.attribute, validation.message(self)) unless validation_result
76
+ increment_times_validated_for(validation)
77
+ validation.run_after_validate(validation_result, self, validation.attribute)
78
+ end
79
+
80
+ def run_before_validations #:nodoc:
81
+ self.class.all_before_validations.each do |block|
82
+ instance_eval &block
83
+ end
84
+ end
85
+
86
+ def add_error(attribute, message) #:nodoc:
87
+ self.class.add_error(self, attribute, message)
88
+ end
89
+
90
+ def validations_for_level_and_group(level, group) #:nodoc:
91
+ validations_for_level = self.all_validations.select { |validation| validation.level == level }
92
+ return validations_for_level.select { |validation| validation.groups.empty? } if group.nil?
93
+ validations_for_level.select { |validation| validation.groups.include?(group) }
94
+ end
95
+
96
+ def all_validations #:nodoc:
97
+ res = self.class.validations_to_include.inject(self.class.all_validations) do |result, included_validation_class|
98
+ result += self.send(included_validation_class.attribute).all_validations
99
+ result
100
+ end
101
+ end
102
+
103
+ def validation_levels #:nodoc:
104
+ self.class.all_validations.inject([1]) { |result, validation| result << validation.level }.uniq.sort
105
+ end
106
+ end
@@ -0,0 +1,14 @@
1
+ module Validatable
2
+ class ValidatesAcceptanceOf < ValidationBase #:nodoc:
3
+ def valid?(instance)
4
+ value = instance.send(self.attribute)
5
+ return true if allow_nil && value.nil?
6
+ return true if allow_blank && value.blank?
7
+ %w(1 true t).include?(value)
8
+ end
9
+
10
+ def message(instance)
11
+ super || "must be accepted"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module Validatable
2
+ class ValidatesAssociated < ValidationBase #:nodoc:
3
+ def valid?(instance)
4
+ Array(instance.send(attribute)).compact.map do |child|
5
+ child.valid?
6
+ end.all?
7
+ end
8
+
9
+ def message(instance)
10
+ super || "is invalid"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ module Validatable
2
+ class ValidatesConfirmationOf < ValidationBase #:nodoc:
3
+ option :case_sensitive
4
+ default :case_sensitive => true
5
+
6
+ def initialize(klass, attribute, options={})
7
+ klass.class_eval { attr_accessor "#{attribute}_confirmation" }
8
+ super
9
+ end
10
+
11
+ def valid?(instance)
12
+ confirmation_value = instance.send("#{self.attribute}_confirmation")
13
+ return true if allow_nil && confirmation_value.nil?
14
+ return true if allow_blank && confirmation_value.blank?
15
+ return instance.send(self.attribute) == instance.send("#{self.attribute}_confirmation".to_sym) if case_sensitive
16
+ instance.send(self.attribute).to_s.casecmp(instance.send("#{self.attribute}_confirmation".to_sym).to_s) == 0
17
+ end
18
+
19
+ def message(instance)
20
+ super || "doesn't match confirmation"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,14 @@
1
+ module Validatable
2
+ class ValidatesEach < ValidationBase #:nodoc:
3
+ required_option :logic
4
+
5
+ def valid?(instance)
6
+ instance.instance_eval(&logic)
7
+ true # return true so no error is added. should look in the future at doing this different.
8
+ end
9
+
10
+ def message(instance)
11
+ super || "is invalid"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ module Validatable
2
+ class ValidatesExclusionOf < ValidationBase #:nodoc:
3
+ required_option :within
4
+
5
+ def valid?(instance)
6
+ value = instance.send(attribute)
7
+ return true if allow_nil && value.nil?
8
+ return true if allow_blank && value.blank?
9
+
10
+ !within.include?(value)
11
+ end
12
+
13
+ def message(instance)
14
+ super || "is reserved"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ module Validatable
2
+ class ValidatesFormatOf < ValidationBase #:nodoc:
3
+ required_option :with
4
+
5
+ def valid?(instance)
6
+ value = instance.send(self.attribute)
7
+ return true if allow_nil && value.nil?
8
+ return true if allow_blank && value.blank?
9
+ not (value.to_s =~ self.with).nil?
10
+ end
11
+
12
+ def message(instance)
13
+ super || "is invalid"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ module Validatable
2
+ class ValidatesInclusionOf < ValidationBase
3
+ required_option :within
4
+
5
+ def valid?(instance)
6
+ value = instance.send(attribute)
7
+ return true if allow_nil && value.nil?
8
+ return true if allow_blank && value.blank?
9
+
10
+ within.include?(value)
11
+ end
12
+
13
+ def message(instance)
14
+ super || "is not in the list"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+ module Validatable
2
+ class ValidatesLengthOf < ValidationBase #:nodoc:
3
+ option :minimum, :maximum, :is, :within
4
+
5
+ def message(instance)
6
+ super || "is invalid"
7
+ end
8
+
9
+ def valid?(instance)
10
+ valid = true
11
+ value = instance.send(self.attribute)
12
+
13
+ if value.nil?
14
+ return true if allow_nil
15
+ value = ''
16
+ end
17
+
18
+ if value.blank?
19
+ return true if allow_blank
20
+ value = ''
21
+ end
22
+
23
+ valid &&= value.length <= maximum unless maximum.nil?
24
+ valid &&= value.length >= minimum unless minimum.nil?
25
+ valid &&= value.length == is unless is.nil?
26
+ valid &&= within.include?(value.length) unless within.nil?
27
+ valid
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ module Validatable
2
+ class ValidatesNumericalityOf < ValidationBase #:nodoc:
3
+ option :only_integer
4
+
5
+ def valid?(instance)
6
+ value = value_for(instance)
7
+ return true if allow_nil && value.nil?
8
+ return true if allow_blank && value.blank?
9
+
10
+ value = value.to_s
11
+ regex = self.only_integer ? /\A[+-]?\d+\Z/ : /\A[+-]?\d*\.{0,1}\d+$/
12
+ not (value =~ regex).nil?
13
+ end
14
+
15
+ def message(instance)
16
+ super || "must be a number"
17
+ end
18
+
19
+ private
20
+ def value_for(instance)
21
+ before_typecast_method = "#{self.attribute}_before_typecast"
22
+ value_method = instance.respond_to?(before_typecast_method.intern) ? before_typecast_method : self.attribute
23
+ instance.send(value_method)
24
+ end
25
+ end
26
+ end
27
+
@@ -0,0 +1,17 @@
1
+ module Validatable
2
+ class ValidatesPresenceOf < ValidationBase #:nodoc:
3
+ def valid?(instance)
4
+ value = instance.send(self.attribute)
5
+ return true if allow_nil && value.nil?
6
+ return true if allow_blank && value.blank?
7
+
8
+ return false if instance.send(self.attribute).nil?
9
+ value.respond_to?(:strip) ? instance.send(self.attribute).strip.length != 0 : true
10
+ end
11
+
12
+ def message(instance)
13
+ super || "can't be empty"
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,13 @@
1
+ module Validatable
2
+ class ValidatesTrueFor < ValidationBase #:nodoc:
3
+ required_option :logic
4
+
5
+ def valid?(instance)
6
+ instance.instance_eval(&logic) == true
7
+ end
8
+
9
+ def message(instance)
10
+ super || "is invalid"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,91 @@
1
+ module Validatable
2
+ class ValidationBase #:nodoc:
3
+ def self.required_option(*args)
4
+ option(*args)
5
+ requires(*args)
6
+ end
7
+
8
+ def self.option(*args)
9
+ attr_accessor(*args)
10
+ understands(*args)
11
+ end
12
+
13
+ def self.default(hash)
14
+ defaults.merge! hash
15
+ end
16
+
17
+ def self.defaults
18
+ @defaults ||= {}
19
+ end
20
+
21
+ def self.all_defaults
22
+ return defaults.merge(self.superclass.all_defaults) if self.superclass.respond_to? :all_defaults
23
+ defaults
24
+ end
25
+
26
+ def self.after_validate(&block)
27
+ after_validations << block
28
+ end
29
+
30
+ def self.after_validations
31
+ @after_validations ||= []
32
+ end
33
+
34
+ def self.all_after_validations
35
+ return after_validations + self.superclass.all_after_validations if self.superclass.respond_to? :all_after_validations
36
+ after_validations
37
+ end
38
+
39
+ include Understandable
40
+ include Requireable
41
+
42
+ option :message, :if, :times, :level, :groups, :key, :after_validate, :allow_nil, :allow_blank
43
+ default :level => 1, :groups => []
44
+ attr_accessor :attribute
45
+
46
+ def initialize(klass, attribute, options={})
47
+ must_understand options
48
+ requires options
49
+ self.class.all_understandings.each do |understanding|
50
+ options[understanding] = self.class.all_defaults[understanding] unless options.has_key? understanding
51
+ self.instance_variable_set("@#{understanding}", options[understanding])
52
+ end
53
+ self.attribute = attribute
54
+ self.groups = [self.groups] unless self.groups.is_a?(Array)
55
+ self.key = "#{klass.name}/#{self.class.name}/#{self.key || self.attribute}"
56
+ raise_error_if_key_is_dup(klass)
57
+ end
58
+
59
+ def raise_error_if_key_is_dup(klass)
60
+ message = "key #{self.key} must be unique, provide the :key option to specify a unique key"
61
+ raise ArgumentError.new(message) if klass.validation_keys_include? self.key
62
+ end
63
+
64
+ def should_validate?(instance)
65
+ result = validate_this_time?(instance)
66
+ case self.if
67
+ when Proc
68
+ result &&= instance.instance_eval(&self.if)
69
+ when Symbol, String
70
+ result &&= instance.instance_eval(self.if.to_s)
71
+ end
72
+ result
73
+ end
74
+
75
+ def message(instance)
76
+ @message.respond_to?(:call) ? instance.instance_eval(&@message) : @message
77
+ end
78
+
79
+ def validate_this_time?(instance)
80
+ return true if @times.nil?
81
+ self.times > instance.times_validated(self.key)
82
+ end
83
+
84
+ def run_after_validate(result, instance, attribute)
85
+ self.class.all_after_validations.each do |block|
86
+ block.call result, instance, attribute
87
+ end
88
+ instance.instance_eval_with_params result, attribute, &self.after_validate unless self.after_validate.nil?
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,36 @@
1
+ # gem 'activesupport', '2.3.5'
2
+
3
+ begin
4
+ require 'active_support/core_ext/object/blank' # newer versions of active_support (>= 3.0)
5
+ rescue LoadError => e
6
+ require 'active_support/all' # support older versions of active_support (<= 2.3.5)
7
+ end
8
+ require 'forwardable'
9
+
10
+ lib_path = File.expand_path('../validatable/', __FILE__)
11
+
12
+ require File.join(lib_path, 'object_extension')
13
+ require File.join(lib_path, 'errors')
14
+ require File.join(lib_path, 'validatable_class_methods')
15
+ require File.join(lib_path, 'macros')
16
+ require File.join(lib_path, 'validatable_instance_methods')
17
+ require File.join(lib_path, 'included_validation')
18
+ require File.join(lib_path, 'child_validation')
19
+ require File.join(lib_path, 'understandable')
20
+ require File.join(lib_path, 'requireable')
21
+ require File.join(lib_path, 'validations/validation_base')
22
+ require File.join(lib_path, 'validations/validates_format_of')
23
+ require File.join(lib_path, 'validations/validates_presence_of')
24
+ require File.join(lib_path, 'validations/validates_acceptance_of')
25
+ require File.join(lib_path, 'validations/validates_confirmation_of')
26
+ require File.join(lib_path, 'validations/validates_length_of')
27
+ require File.join(lib_path, 'validations/validates_true_for')
28
+ require File.join(lib_path, 'validations/validates_numericality_of')
29
+ require File.join(lib_path, 'validations/validates_exclusion_of')
30
+ require File.join(lib_path, 'validations/validates_inclusion_of')
31
+ require File.join(lib_path, 'validations/validates_each')
32
+ require File.join(lib_path, 'validations/validates_associated')
33
+
34
+ module Validatable
35
+ Version = '1.8.6'
36
+ end