mattmatt-validatable 1.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. data/README.rdoc +116 -0
  2. data/Rakefile +33 -0
  3. data/VERSION.yml +5 -0
  4. data/lib/validatable.rb +28 -0
  5. data/lib/validatable/child_validation.rb +15 -0
  6. data/lib/validatable/errors.rb +93 -0
  7. data/lib/validatable/included_validation.rb +9 -0
  8. data/lib/validatable/macros.rb +314 -0
  9. data/lib/validatable/object_extension.rb +21 -0
  10. data/lib/validatable/requireable.rb +26 -0
  11. data/lib/validatable/understandable.rb +31 -0
  12. data/lib/validatable/validatable_class_methods.rb +87 -0
  13. data/lib/validatable/validatable_instance_methods.rb +106 -0
  14. data/lib/validatable/validations/validates_acceptance_of.rb +14 -0
  15. data/lib/validatable/validations/validates_associated.rb +13 -0
  16. data/lib/validatable/validations/validates_confirmation_of.rb +23 -0
  17. data/lib/validatable/validations/validates_each.rb +14 -0
  18. data/lib/validatable/validations/validates_exclusion_of.rb +17 -0
  19. data/lib/validatable/validations/validates_format_of.rb +16 -0
  20. data/lib/validatable/validations/validates_inclusion_of.rb +17 -0
  21. data/lib/validatable/validations/validates_length_of.rb +30 -0
  22. data/lib/validatable/validations/validates_numericality_of.rb +27 -0
  23. data/lib/validatable/validations/validates_presence_of.rb +17 -0
  24. data/lib/validatable/validations/validates_true_for.rb +13 -0
  25. data/lib/validatable/validations/validation_base.rb +91 -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 +33 -0
  39. data/test/unit/test_errors.rb +82 -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 +76 -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 +133 -0
@@ -0,0 +1,21 @@
1
+ class Object #:nodoc:
2
+ module InstanceExecHelper #:nodoc:
3
+ end
4
+ include InstanceExecHelper
5
+ def instance_eval_with_params(*args, &block)
6
+ begin
7
+ old_critical, Thread.critical = Thread.critical, true
8
+ n = 0
9
+ n += 1 while respond_to?(mname="__instance_exec#{n}")
10
+ InstanceExecHelper.module_eval{ define_method(mname, &block) }
11
+ ensure
12
+ Thread.critical = old_critical
13
+ end
14
+ begin
15
+ ret = send(mname, *args)
16
+ ensure
17
+ InstanceExecHelper.module_eval{ remove_method(mname) } rescue nil
18
+ end
19
+ ret
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ module Validatable
2
+ module Requireable #:nodoc:
3
+ module ClassMethods #:nodoc:
4
+ def requires(*args)
5
+ required_options.concat args
6
+ end
7
+
8
+ def required_options
9
+ @required_options ||= []
10
+ end
11
+ end
12
+
13
+ def self.included(klass)
14
+ klass.extend ClassMethods
15
+ end
16
+
17
+ def requires(options)
18
+ required_options = self.class.required_options.inject([]) do |errors, attribute|
19
+ errors << attribute.to_s unless options.has_key?(attribute)
20
+ errors
21
+ end
22
+ raise ArgumentError.new("#{self.class} requires options: #{required_options.join(', ')}") if required_options.any?
23
+ true
24
+ end
25
+ end
26
+ end
@@ -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/ : /^\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
+