dm-validations 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
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