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.
- data/LICENSE +20 -0
- data/README +72 -0
- data/Rakefile +74 -0
- data/TODO +16 -0
- data/lib/dm-validations.rb +208 -0
- data/lib/dm-validations/absent_field_validator.rb +60 -0
- data/lib/dm-validations/acceptance_validator.rb +76 -0
- data/lib/dm-validations/auto_validate.rb +98 -0
- data/lib/dm-validations/confirmation_validator.rb +76 -0
- data/lib/dm-validations/contextual_validators.rb +56 -0
- data/lib/dm-validations/custom_validator.rb +72 -0
- data/lib/dm-validations/format_validator.rb +94 -0
- data/lib/dm-validations/formats/email.rb +40 -0
- data/lib/dm-validations/generic_validator.rb +92 -0
- data/lib/dm-validations/length_validator.rb +113 -0
- data/lib/dm-validations/method_validator.rb +58 -0
- data/lib/dm-validations/numeric_validator.rb +66 -0
- data/lib/dm-validations/primitive_validator.rb +60 -0
- data/lib/dm-validations/required_field_validator.rb +88 -0
- data/lib/dm-validations/support/object.rb +5 -0
- data/lib/dm-validations/uniqueness_validator.rb +61 -0
- data/lib/dm-validations/validation_errors.rb +63 -0
- data/lib/dm-validations/within_validator.rb +41 -0
- data/spec/integration/absent_field_validator_spec.rb +34 -0
- data/spec/integration/acceptance_validator_spec.rb +87 -0
- data/spec/integration/auto_validate_spec.rb +262 -0
- data/spec/integration/confirmation_validator_spec.rb +66 -0
- data/spec/integration/contextual_validators_spec.rb +28 -0
- data/spec/integration/custom_validator_spec.rb +9 -0
- data/spec/integration/format_validator_spec.rb +118 -0
- data/spec/integration/generic_validator_spec.rb +9 -0
- data/spec/integration/length_validator_spec.rb +113 -0
- data/spec/integration/method_validator_spec.rb +31 -0
- data/spec/integration/numeric_validator_spec.rb +192 -0
- data/spec/integration/primitive_validator_spec.rb +25 -0
- data/spec/integration/required_field_validator_spec.rb +93 -0
- data/spec/integration/uniqueness_validator_spec.rb +81 -0
- data/spec/integration/validation_errors_spec.rb +18 -0
- data/spec/integration/validation_spec.rb +339 -0
- data/spec/integration/within_validator_spec.rb +35 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +26 -0
- 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
|