dm-validations 0.9.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 (43) hide show
  1. data/LICENSE +20 -0
  2. data/README +72 -0
  3. data/Rakefile +74 -0
  4. data/TODO +16 -0
  5. data/lib/dm-validations.rb +208 -0
  6. data/lib/dm-validations/absent_field_validator.rb +60 -0
  7. data/lib/dm-validations/acceptance_validator.rb +76 -0
  8. data/lib/dm-validations/auto_validate.rb +98 -0
  9. data/lib/dm-validations/confirmation_validator.rb +76 -0
  10. data/lib/dm-validations/contextual_validators.rb +56 -0
  11. data/lib/dm-validations/custom_validator.rb +72 -0
  12. data/lib/dm-validations/format_validator.rb +94 -0
  13. data/lib/dm-validations/formats/email.rb +40 -0
  14. data/lib/dm-validations/generic_validator.rb +92 -0
  15. data/lib/dm-validations/length_validator.rb +113 -0
  16. data/lib/dm-validations/method_validator.rb +58 -0
  17. data/lib/dm-validations/numeric_validator.rb +66 -0
  18. data/lib/dm-validations/primitive_validator.rb +60 -0
  19. data/lib/dm-validations/required_field_validator.rb +88 -0
  20. data/lib/dm-validations/support/object.rb +5 -0
  21. data/lib/dm-validations/uniqueness_validator.rb +61 -0
  22. data/lib/dm-validations/validation_errors.rb +63 -0
  23. data/lib/dm-validations/within_validator.rb +41 -0
  24. data/spec/integration/absent_field_validator_spec.rb +34 -0
  25. data/spec/integration/acceptance_validator_spec.rb +87 -0
  26. data/spec/integration/auto_validate_spec.rb +262 -0
  27. data/spec/integration/confirmation_validator_spec.rb +66 -0
  28. data/spec/integration/contextual_validators_spec.rb +28 -0
  29. data/spec/integration/custom_validator_spec.rb +9 -0
  30. data/spec/integration/format_validator_spec.rb +118 -0
  31. data/spec/integration/generic_validator_spec.rb +9 -0
  32. data/spec/integration/length_validator_spec.rb +113 -0
  33. data/spec/integration/method_validator_spec.rb +31 -0
  34. data/spec/integration/numeric_validator_spec.rb +192 -0
  35. data/spec/integration/primitive_validator_spec.rb +25 -0
  36. data/spec/integration/required_field_validator_spec.rb +93 -0
  37. data/spec/integration/uniqueness_validator_spec.rb +81 -0
  38. data/spec/integration/validation_errors_spec.rb +18 -0
  39. data/spec/integration/validation_spec.rb +339 -0
  40. data/spec/integration/within_validator_spec.rb +35 -0
  41. data/spec/spec.opts +2 -0
  42. data/spec/spec_helper.rb +26 -0
  43. metadata +104 -0
@@ -0,0 +1,76 @@
1
+ module DataMapper
2
+ module Validate
3
+
4
+ ##
5
+ #
6
+ # @author Martin Kihlgren
7
+ # @since 0.9
8
+ class AcceptanceValidator < GenericValidator
9
+
10
+ def self.default_message_for_field(field_name)
11
+ '%s is not accepted'.t(Extlib::Inflection.humanize(field_name))
12
+ end
13
+
14
+ def initialize(field_name, options = {})
15
+ super
16
+ @options = options
17
+ @field_name = field_name
18
+ @options[:allow_nil] = true unless @options.include?(:allow_nil)
19
+ @options[:accept] ||= ["1",1,"true",true,"t"]
20
+ @options[:accept] = Array(@options[:accept])
21
+ end
22
+
23
+ def call(target)
24
+ unless valid?(target)
25
+ error_message = @options[:message] || DataMapper::Validate::AcceptanceValidator.default_message_for_field(@field_name)
26
+ add_error(target, error_message , @field_name)
27
+ return false
28
+ end
29
+
30
+ return true
31
+ end
32
+
33
+ def valid?(target)
34
+ field_value = target.instance_variable_get("@#{@field_name}")
35
+ return true if @options[:allow_nil] && field_value.nil?
36
+ return false if !@options[:allow_nil] && field_value.nil?
37
+
38
+ @options[:accept].include?(field_value)
39
+ end
40
+
41
+ end # class AcceptanceValidator
42
+
43
+ module ValidatesIsAccepted
44
+
45
+ ##
46
+ # Validates that the attributes's value is in the set of accepted values.
47
+ #
48
+ # @option :allow_nil<Boolean> true if nil is allowed, false if nil is not
49
+ # allowed. Default is true.
50
+ # @option :accept<Array> a list of accepted values.
51
+ # Default are ["1",1,"true",true,"t"]).
52
+ #
53
+ # @example [Usage]
54
+ # require 'dm-validations'
55
+ #
56
+ # class Page
57
+ # include DataMapper::Resource
58
+ #
59
+ # property :license_agreement_accepted, String
60
+ # property :terms_accepted, String
61
+ # validates_is_accepted :license_agreement, :accept => "1"
62
+ # validates_is_accepted :terms_accepted, :allow_nil => false
63
+ #
64
+ # # a call to valid? will return false unless:
65
+ # # license_agreement is nil or "1"
66
+ # # and
67
+ # # terms_accepted is one of ["1",1,"true",true,"t"]
68
+ #
69
+ def validates_is_accepted(*fields)
70
+ opts = opts_from_validator_args(fields)
71
+ add_validator_to_context(opts, fields, DataMapper::Validate::AcceptanceValidator)
72
+ end
73
+
74
+ end # module ValidatesIsAccepted
75
+ end # module Validate
76
+ end # module DataMapper
@@ -0,0 +1,98 @@
1
+ module DataMapper
2
+ module Validate
3
+ module AutoValidate
4
+
5
+ ##
6
+ # Auto-generate validations for a given property. This will only occur
7
+ # if the option :auto_validation is either true or left undefined.
8
+ #
9
+ # @details [Triggers]
10
+ # Triggers that generate validator creation
11
+ #
12
+ # :nullable => false
13
+ # Setting the option :nullable to false causes a
14
+ # validates_presence_of validator to be automatically created on
15
+ # the property
16
+ #
17
+ # :size => 20 or :length => 20
18
+ # Setting the option :size or :length causes a validates_length_of
19
+ # validator to be automatically created on the property. If the
20
+ # value is a Integer the validation will set :maximum => value if
21
+ # the value is a Range the validation will set :within => value
22
+ #
23
+ # :format => :predefined / lambda / Proc
24
+ # Setting the :format option causes a validates_format_of
25
+ # validator to be automatically created on the property
26
+ #
27
+ # Integer type
28
+ # Using a Integer type causes a validates_is_number
29
+ # validator to be created for the property. integer_only
30
+ # is set to true
31
+ #
32
+ # BigDecimal or Float type
33
+ # Using a Integer type causes a validates_is_number
34
+ # validator to be created for the property. integer_only
35
+ # is set to false, and precision/scale match the property
36
+ def auto_generate_validations(property)
37
+ property.options[:auto_validation] = true unless property.options.has_key?(:auto_validation)
38
+ return unless property.options[:auto_validation]
39
+
40
+ # a serial property is allowed to be nil too, because the
41
+ # value is set by the storage system
42
+ opts = { :allow_nil => property.nullable? || property.serial? }
43
+ opts[:context] = property.options[:validates] if property.options.has_key?(:validates)
44
+
45
+ # presence
46
+ unless opts[:allow_nil]
47
+ validates_present property.name, opts
48
+ end
49
+
50
+ # length
51
+ if property.type == String
52
+ #len = property.length # XXX: maybe length should always return a Range, with the min defaulting to 1
53
+ len = property.options.fetch(:length, property.options.fetch(:size, DataMapper::Property::DEFAULT_LENGTH))
54
+ if len.is_a?(Range)
55
+ opts[:within] = len
56
+ else
57
+ opts[:maximum] = len
58
+ end
59
+ validates_length property.name, opts
60
+ end
61
+
62
+ # format
63
+ if property.options.has_key?(:format)
64
+ opts[:with] = property.options[:format]
65
+ validates_format property.name, opts
66
+ end
67
+
68
+ # uniqueness validator
69
+ if property.options.has_key?(:unique)
70
+ value = property.options[:unique]
71
+ if value.is_a?(Array) || value.is_a?(Symbol)
72
+ validates_is_unique property.name, :scope => Array(value)
73
+ elsif value.is_a?(TrueClass)
74
+ validates_is_unique property.name
75
+ end
76
+ end
77
+
78
+ # numeric validator
79
+ if Integer == property.type
80
+ opts[:integer_only] = true
81
+ validates_is_number property.name, opts
82
+ elsif BigDecimal == property.type || Float == property.type
83
+ opts[:precision] = property.precision
84
+ opts[:scale] = property.scale
85
+ validates_is_number property.name, opts
86
+ else
87
+ # We only need this in the case we don't already
88
+ # have a numeric validator, because otherwise
89
+ # it will cause duplicate validation errors
90
+ unless property.custom?
91
+ validates_is_primitive property.name, opts
92
+ end
93
+ end
94
+ end
95
+
96
+ end # module AutoValidate
97
+ end # module Validate
98
+ end # module DataMapper
@@ -0,0 +1,76 @@
1
+ module DataMapper
2
+ module Validate
3
+
4
+ ##
5
+ #
6
+ # @author Guy van den Berg
7
+ # @since 0.9
8
+ class ConfirmationValidator < GenericValidator
9
+
10
+ def initialize(field_name, options = {})
11
+ super
12
+ @options = options
13
+ @field_name, @confirm_field_name = field_name, (options[:confirm] || "#{field_name}_confirmation").to_sym
14
+ @options[:allow_nil] = true unless @options.has_key?(:allow_nil)
15
+ end
16
+
17
+ def call(target)
18
+ unless valid?(target)
19
+ error_message = @options[:message] || '%s does not match the confirmation'.t(Extlib::Inflection.humanize(@field_name))
20
+ add_error(target, error_message , @field_name)
21
+ return false
22
+ end
23
+
24
+ return true
25
+ end
26
+
27
+ def valid?(target)
28
+ field_value = target.instance_variable_get("@#{@field_name}")
29
+ return true if @options[:allow_nil] && field_value.nil?
30
+ return false if !@options[:allow_nil] && field_value.nil?
31
+
32
+ confirm_value = target.instance_variable_get("@#{@confirm_field_name}")
33
+ field_value == confirm_value
34
+ end
35
+
36
+ end # class ConfirmationValidator
37
+
38
+ module ValidatesIsConfirmed
39
+
40
+ ##
41
+ # Validates that the given attribute is confirmed by another attribute.
42
+ # A common use case scenario is when you require a user to confirm their
43
+ # password, for which you use both password and password_confirmation
44
+ # attributes.
45
+ #
46
+ # @option :allow_nil<Boolean> true/false (default is true)
47
+ # @option :confirm<Symbol> the attribute that you want to validate
48
+ # against (default is firstattr_confirmation)
49
+ #
50
+ # @example [Usage]
51
+ # require 'dm-validations'
52
+ #
53
+ # class Page
54
+ # include DataMapper::Resource
55
+ #
56
+ # property :password, String
57
+ # property :email, String
58
+ # attr_accessor :password_confirmation
59
+ # attr_accessor :email_repeated
60
+ #
61
+ # validates_is_confirmed :password
62
+ # validates_is_confirmed :email, :confirm => :email_repeated
63
+ #
64
+ # # a call to valid? will return false unless:
65
+ # # password == password_confirmation
66
+ # # and
67
+ # # email == email_repeated
68
+ #
69
+ def validates_is_confirmed(*fields)
70
+ opts = opts_from_validator_args(fields)
71
+ add_validator_to_context(opts, fields, DataMapper::Validate::ConfirmationValidator)
72
+ end
73
+
74
+ end # module ValidatesIsConfirmed
75
+ end # module Validate
76
+ end # module DataMapper
@@ -0,0 +1,56 @@
1
+ module DataMapper
2
+ module Validate
3
+
4
+ ##
5
+ #
6
+ # @author Guy van den Berg
7
+ # @since 0.9
8
+ class ContextualValidators
9
+
10
+ def dump
11
+ contexts.each_pair do |key,context|
12
+ puts "Key=#{key} Context: #{context}"
13
+ end
14
+ end
15
+
16
+ # Get a hash of named context validators for the resource
17
+ #
18
+ # @return <Hash> a hash of validators <GenericValidator>
19
+ def contexts
20
+ @contexts ||= @contexts = {}
21
+ end
22
+
23
+ # Return an array of validators for a named context
24
+ #
25
+ # @return <Array> An array of validators
26
+ def context(name)
27
+ contexts[name] = [] unless contexts.has_key?(name)
28
+ contexts[name]
29
+ end
30
+
31
+ # Clear all named context validators off of the resource
32
+ #
33
+ def clear!
34
+ contexts.clear
35
+ end
36
+
37
+ # Execute all validators in the named context against the target
38
+ #
39
+ # @param <Symbol> named_context the context we are validating against
40
+ # @param <Object> target the resource that we are validating
41
+ # @return <Boolean> true if all are valid, otherwise false
42
+ def execute(named_context, target)
43
+ raise(ArgumentError, 'invalid context specified') if !named_context || (contexts.length > 0 && !contexts[named_context])
44
+ target.errors.clear!
45
+ result = true
46
+ context(named_context).each do |validator|
47
+ if validator.execute?(target)
48
+ result = false if !validator.call(target)
49
+ end
50
+ end
51
+ return result
52
+ end
53
+
54
+ end # module ContextualValidators
55
+ end # module Validate
56
+ end # module DataMapper
@@ -0,0 +1,72 @@
1
+ #require File.dirname(__FILE__) + '/formats/email'
2
+
3
+ module DataMapper
4
+ module Validate
5
+
6
+ ##
7
+ #
8
+ # @author Guy van den Berg
9
+ # @since 0.9
10
+ class CustomValidator < GenericValidator
11
+
12
+ def initialize(field_name, options = {}, &b)
13
+ #super(field_name, options)
14
+ #@field_name, @options = field_name, options
15
+ #@options[:allow_nil] = false unless @options.has_key?(:allow_nil)
16
+ end
17
+
18
+ def call(target)
19
+ #field_value = target.instance_variable_get("@#{@field_name}")
20
+ #return true if @options[:allow_nil] && field_value.nil?
21
+
22
+ #validation = (@options[:as] || @options[:with])
23
+ #error_message = nil
24
+
25
+ # Figure out what to use as the actual validator.
26
+ # If a symbol is passed to :as, look up
27
+ # the canned validation in FORMATS.
28
+ #
29
+ #validator = if validation.is_a? Symbol
30
+ # if FORMATS[validation].is_a? Array
31
+ # error_message = FORMATS[validation][1]
32
+ # FORMATS[validation][0]
33
+ # else
34
+ # FORMATS[validation] || validation
35
+ # end
36
+ #else
37
+ # validation
38
+ #end
39
+
40
+ #valid = case validator
41
+ #when Proc then validator.call(field_value)
42
+ #when Regexp then validator =~ field_value
43
+ #else raise UnknownValidationFormat, "Can't determine how to validate #{target.class}##{@field_name} with #{validator.inspect}"
44
+ #end
45
+
46
+ #unless valid
47
+ # field = Inflector.humanize(@field_name)
48
+ # value = field_value
49
+ #
50
+ # error_message = @options[:message] || error_message || '%s is invalid'.t(field)
51
+ # error_message = error_message.call(field, value) if Proc === error_message
52
+ #
53
+ # add_error(target, error_message , @field_name)
54
+ #end
55
+
56
+ #return valid
57
+ end
58
+
59
+ #class UnknownValidationFormat < StandardError; end
60
+
61
+ end # class CustomValidator
62
+
63
+ module ValidatesFormatOf
64
+
65
+ #def validates_format_of(*fields)
66
+ #opts = opts_from_validator_args(fields)
67
+ #add_validator_to_context(opts, fields, DataMapper::Validate::FormatValidator)
68
+ #end
69
+
70
+ end # module ValidatesFormatOf
71
+ end # module Validate
72
+ end # module DataMapper
@@ -0,0 +1,94 @@
1
+ #require File.dirname(__FILE__) + '/formats/email'
2
+
3
+ require 'pathname'
4
+ require Pathname(__FILE__).dirname.expand_path + 'formats/email'
5
+
6
+ module DataMapper
7
+ module Validate
8
+
9
+ ##
10
+ #
11
+ # @author Guy van den Berg
12
+ # @since 0.9
13
+ class FormatValidator < GenericValidator
14
+
15
+ FORMATS = {}
16
+ include DataMapper::Validate::Format::Email
17
+
18
+ def initialize(field_name, options = {}, &b)
19
+ super(field_name, options)
20
+ @field_name, @options = field_name, options
21
+ @options[:allow_nil] = false unless @options.has_key?(:allow_nil)
22
+ end
23
+
24
+ def call(target)
25
+ value = target.validation_property_value(@field_name)
26
+ return true if @options[:allow_nil] && value.nil?
27
+
28
+ validation = (@options[:as] || @options[:with])
29
+
30
+ raise "No such predefined format '#{validation}'" if validation.is_a?(Symbol) && !FORMATS.has_key?(validation)
31
+ validator = validation.is_a?(Symbol) ? FORMATS[validation][0] : validation
32
+
33
+ field = Extlib::Inflection.humanize(@field_name)
34
+ error_message = @options[:message] || '%s has an invalid format'.t(field)
35
+
36
+ valid = case validator
37
+ when Proc then validator.call(value)
38
+ when Regexp then validator =~ value
39
+ else raise UnknownValidationFormat, "Can't determine how to validate #{target.class}##{@field_name} with #{validator.inspect}"
40
+ end
41
+
42
+ unless valid
43
+ error_message = @options[:message] || error_message || '%s is invalid'.t(field)
44
+ error_message = error_message.call(field, value) if Proc === error_message
45
+ add_error(target, error_message , @field_name)
46
+ end
47
+
48
+ return valid
49
+ end
50
+
51
+ #class UnknownValidationFormat < StandardError; end
52
+
53
+ end # class FormatValidator
54
+
55
+ module ValidatesFormat
56
+
57
+ ##
58
+ # Validates that the attribute is in the specified format. You may use the
59
+ # :as (or :with, it's an alias) option to specify the pre-defined format
60
+ # that you want to validate against. You may also specify your own format
61
+ # via a Proc or Regexp passed to the the :as or :with options.
62
+ #
63
+ # @option :allow_nil<Boolean> true/false (default is true)
64
+ # @option :as<Format, Proc, Regexp> the pre-defined format, Proc or Regexp to validate against
65
+ # @option :with<Format, Proc, Regexp> an alias for :as
66
+ #
67
+ # @details [Pre-defined Formats]
68
+ # :email_address (format is specified in DataMapper::Validate::Format::Email)
69
+ #
70
+ # @example [Usage]
71
+ # require 'dm-validations'
72
+ #
73
+ # class Page
74
+ # include DataMapper::Resource
75
+ #
76
+ # property :email, String
77
+ # property :zip_code, String
78
+ #
79
+ # validates_format :email, :as => :email_address
80
+ # validates_format :zip_code, :with => /^\d{5}$/
81
+ #
82
+ # # a call to valid? will return false unless:
83
+ # # email is formatted like an email address
84
+ # # and
85
+ # # zip_code is a string of 5 digits
86
+ #
87
+ def validates_format(*fields)
88
+ opts = opts_from_validator_args(fields)
89
+ add_validator_to_context(opts, fields, DataMapper::Validate::FormatValidator)
90
+ end
91
+
92
+ end # module ValidatesFormat
93
+ end # module Validate
94
+ end # module DataMapper