ramsingla-validatable 1.7.2

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 (45) hide show
  1. data/README.rdoc +123 -0
  2. data/Rakefile +54 -0
  3. data/VERSION.yml +4 -0
  4. data/lib/child_validation.rb +15 -0
  5. data/lib/errors.rb +105 -0
  6. data/lib/included_validation.rb +9 -0
  7. data/lib/macros.rb +284 -0
  8. data/lib/object_extension.rb +21 -0
  9. data/lib/requireable.rb +26 -0
  10. data/lib/understandable.rb +31 -0
  11. data/lib/validatable.rb +24 -0
  12. data/lib/validatable_class_methods.rb +85 -0
  13. data/lib/validatable_instance_methods.rb +116 -0
  14. data/lib/validations/validates_acceptance_of.rb +14 -0
  15. data/lib/validations/validates_confirmation_of.rb +23 -0
  16. data/lib/validations/validates_each.rb +14 -0
  17. data/lib/validations/validates_format_of.rb +16 -0
  18. data/lib/validations/validates_length_of.rb +30 -0
  19. data/lib/validations/validates_numericality_of.rb +27 -0
  20. data/lib/validations/validates_presence_of.rb +17 -0
  21. data/lib/validations/validates_true_for.rb +13 -0
  22. data/lib/validations/validation_base.rb +86 -0
  23. data/test/all_tests.rb +1 -0
  24. data/test/functional/test_validatable.rb +553 -0
  25. data/test/functional/test_validates_acceptance_of.rb +16 -0
  26. data/test/functional/test_validates_confirmation_of.rb +56 -0
  27. data/test/functional/test_validates_each.rb +14 -0
  28. data/test/functional/test_validates_format_of.rb +34 -0
  29. data/test/functional/test_validates_length_of.rb +64 -0
  30. data/test/functional/test_validates_numericality_of.rb +57 -0
  31. data/test/functional/test_validates_presence_of.rb +16 -0
  32. data/test/functional/test_validates_true_for.rb +27 -0
  33. data/test/test_helper.rb +35 -0
  34. data/test/unit/test_errors.rb +64 -0
  35. data/test/unit/test_understandable.rb +19 -0
  36. data/test/unit/test_validatable.rb +38 -0
  37. data/test/unit/test_validates_acceptance_of.rb +45 -0
  38. data/test/unit/test_validates_confirmation_of.rb +76 -0
  39. data/test/unit/test_validates_format_of.rb +44 -0
  40. data/test/unit/test_validates_length_of.rb +80 -0
  41. data/test/unit/test_validates_numericality_of.rb +76 -0
  42. data/test/unit/test_validates_presence_of.rb +35 -0
  43. data/test/unit/test_validates_true_for.rb +29 -0
  44. data/test/unit/test_validation_base.rb +52 -0
  45. metadata +119 -0
@@ -0,0 +1,24 @@
1
+ require 'forwardable'
2
+ require 'rubygems'
3
+ require 'activesupport'
4
+
5
+ dir = File.expand_path(File.dirname(__FILE__))
6
+
7
+ require File.join(dir, 'object_extension')
8
+ require File.join(dir, 'errors')
9
+ require File.join(dir, 'validatable_class_methods')
10
+ require File.join(dir, 'macros')
11
+ require File.join(dir, 'validatable_instance_methods')
12
+ require File.join(dir, 'included_validation')
13
+ require File.join(dir, 'child_validation')
14
+ require File.join(dir, 'understandable')
15
+ require File.join(dir, 'requireable')
16
+ require File.join(dir, 'validations/validation_base')
17
+ require File.join(dir, 'validations/validates_format_of')
18
+ require File.join(dir, 'validations/validates_presence_of')
19
+ require File.join(dir, 'validations/validates_acceptance_of')
20
+ require File.join(dir, 'validations/validates_confirmation_of')
21
+ require File.join(dir, 'validations/validates_length_of')
22
+ require File.join(dir, 'validations/validates_true_for')
23
+ require File.join(dir, 'validations/validates_numericality_of')
24
+ require File.join(dir, 'validations/validates_each')
@@ -0,0 +1,85 @@
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 = instance.send child_validation.attribute
8
+ if (child.respond_to?(:valid_for_group?))
9
+ child.valid_for_group?(group)
10
+ else
11
+ child.valid?
12
+ end
13
+ child.errors.each do |attribute, messages|
14
+ if messages.is_a?(String)
15
+ add_error(instance, child_validation.map[attribute.to_sym] || attribute, messages)
16
+ else
17
+ messages.each do |message|
18
+ add_error(instance, child_validation.map[attribute.to_sym] || attribute, message)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ def all_before_validations
26
+ if self.superclass.respond_to? :all_before_validations
27
+ return before_validations + self.superclass.all_before_validations
28
+ end
29
+ before_validations
30
+ end
31
+
32
+ def before_validations
33
+ @before_validations ||= []
34
+ end
35
+
36
+ def all_validations
37
+ if self.respond_to?(:superclass) && self.superclass.respond_to?(:all_validations)
38
+ return validations + self.superclass.all_validations
39
+ end
40
+ validations
41
+ end
42
+
43
+ def validations
44
+ @validations ||= []
45
+ end
46
+
47
+ def add_error(instance, attribute, msg)
48
+ instance.errors.add(attribute, msg)
49
+ end
50
+
51
+ def validation_keys_include?(key)
52
+ validations.map { |validation| validation.key }.include?(key)
53
+ end
54
+
55
+ def validations_to_include
56
+ @validations_to_include ||= []
57
+ end
58
+
59
+ protected
60
+
61
+ def add_validations(args, klass)
62
+ options = args.last.is_a?(Hash) ? args.pop : {}
63
+ args.each do |attribute|
64
+ new_validation = klass.new self, attribute, options
65
+ self.validations << new_validation
66
+ self.create_valid_method_for_groups new_validation.groups
67
+ end
68
+ end
69
+
70
+ def create_valid_method_for_groups(groups)
71
+ groups.each do |group|
72
+ self.class_eval do
73
+ define_method "valid_for_#{group}?".to_sym do
74
+ valid_for_group?(group)
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ def children_to_validate
81
+ @children_to_validate ||= []
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,116 @@
1
+ module Validatable
2
+ def self.included(klass) #:nodoc:
3
+ klass.extend Validatable::ClassMethods
4
+ klass.extend Validatable::Macros
5
+ klass.class_eval do
6
+ include ActiveSupport::Callbacks
7
+ define_callbacks :validate, :validate_on_create, :validate_on_update
8
+ end
9
+ end
10
+
11
+ # call-seq: valid?
12
+ #
13
+ # Returns true if no errors were added otherwise false. Only executes validations that have no :groups option specified
14
+ def valid?
15
+ valid_for_group?(nil)
16
+ end
17
+
18
+ # call-seq: errors
19
+ #
20
+ # Returns the Errors object that holds all information about attribute error messages.
21
+ def errors
22
+ @errors ||= Validatable::Errors.new
23
+ end
24
+
25
+ def valid_for_group?(group) #:nodoc:
26
+ run_before_validations
27
+ errors.clear
28
+ run_callbacks(:validate)
29
+
30
+ if respond_to?(:new?)
31
+ new? ? run_callbacks(:validate_on_create) : run_callbacks(:validate_on_update)
32
+ end
33
+
34
+ self.class.validate_children(self, group)
35
+ self.validate_group(group)
36
+ errors.empty?
37
+ end
38
+
39
+ def times_validated(key) #:nodoc:
40
+ times_validated_hash[key] || 0
41
+ end
42
+
43
+ def increment_times_validated_for(validation) #:nodoc:
44
+ if validation.key != nil
45
+ if times_validated_hash[validation.key].nil?
46
+ times_validated_hash[validation.key] = 1
47
+ else
48
+ times_validated_hash[validation.key] += 1
49
+ end
50
+ end
51
+ end
52
+
53
+ # call-seq: validate_only(key)
54
+ #
55
+ # Only executes a specified validation. The argument should follow a pattern based on the key of the validation.
56
+ # Examples:
57
+ # * validates_presence_of :name can be run with obj.validate_only("presence_of/name")
58
+ # * validates_presence_of :birthday, :key => "a key" can be run with obj.validate_only("presence_of/a key")
59
+ def validate_only(key)
60
+ validation_name, attribute_name = key.split("/")
61
+ validation_name = validation_name.split("_").collect{|word| word.capitalize}.join
62
+ validation_key = "#{self.class.name}/Validatable::Validates#{validation_name}/#{attribute_name}"
63
+ validation = self.class.all_validations.find { |validation| validation.key == validation_key }
64
+ raise ArgumentError.new("validation with key #{validation_key} could not be found") if validation.nil?
65
+ errors.clear
66
+ run_validation(validation)
67
+ end
68
+
69
+ protected
70
+ def times_validated_hash #:nodoc:
71
+ @times_validated_hash ||= {}
72
+ end
73
+
74
+ def validate_group(group) #:nodoc:
75
+ validation_levels.each do |level|
76
+ validations_for_level_and_group(level, group).each do |validation|
77
+ run_validation(validation) if validation.should_validate?(self)
78
+ end
79
+ return unless self.errors.empty?
80
+ end
81
+ end
82
+
83
+ def run_validation(validation) #:nodoc:
84
+ validation_result = validation.valid?(self)
85
+ add_error(validation.attribute, validation.message(self)) unless validation_result
86
+ increment_times_validated_for(validation)
87
+ validation.run_after_validate(validation_result, self, validation.attribute)
88
+ end
89
+
90
+ def run_before_validations #:nodoc:
91
+ self.class.all_before_validations.each do |block|
92
+ instance_eval &block
93
+ end
94
+ end
95
+
96
+ def add_error(attribute, message) #:nodoc:
97
+ self.class.add_error(self, attribute, message)
98
+ end
99
+
100
+ def validations_for_level_and_group(level, group) #:nodoc:
101
+ validations_for_level = self.all_validations.select { |validation| validation.level == level }
102
+ return validations_for_level.select { |validation| validation.groups.empty? } if group.nil?
103
+ validations_for_level.select { |validation| validation.groups.include?(group) }
104
+ end
105
+
106
+ def all_validations #:nodoc:
107
+ res = self.class.validations_to_include.inject(self.class.all_validations) do |result, included_validation_class|
108
+ result += self.send(included_validation_class.attribute).all_validations
109
+ result
110
+ end
111
+ end
112
+
113
+ def validation_levels #:nodoc:
114
+ self.class.all_validations.inject([1]) { |result, validation| result << validation.level }.uniq.sort
115
+ end
116
+ 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 || Validatable::Errors::MessageCodeFor[:accept]
12
+ end
13
+ end
14
+ 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 || Validatable::Errors::MessageCodeFor[: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 || Validatable::Errors::MessageCodeFor[:logic]
12
+ end
13
+ end
14
+ 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 || Validatable::Errors::MessageCodeFor[:format]
14
+ end
15
+ end
16
+ 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 || Validatable::Errors::MessageCodeFor[:length]
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 || Validatable::Errors::MessageCodeFor[:numeric]
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 || Validatable::Errors::MessageCodeFor[:required]
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 || Validatable::Errors::MessageCodeFor[:logic]
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,86 @@
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
+ result &&= instance.instance_eval(&self.if) unless self.if.nil?
67
+ result
68
+ end
69
+
70
+ def message(instance)
71
+ @message.respond_to?(:call) ? instance.instance_eval(&@message) : @message
72
+ end
73
+
74
+ def validate_this_time?(instance)
75
+ return true if @times.nil?
76
+ self.times > instance.times_validated(self.key)
77
+ end
78
+
79
+ def run_after_validate(result, instance, attribute)
80
+ self.class.all_after_validations.each do |block|
81
+ block.call result, instance, attribute
82
+ end
83
+ instance.instance_eval_with_params result, attribute, &self.after_validate unless self.after_validate.nil?
84
+ end
85
+ end
86
+ end
data/test/all_tests.rb ADDED
@@ -0,0 +1 @@
1
+ Dir['test/**/test_*.rb'].each { |test_case| require test_case }