dm-validations 1.0.2 → 1.1.0.rc1
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/Gemfile +25 -94
- data/LICENSE +1 -1
- data/Rakefile +2 -7
- data/VERSION +1 -1
- data/dm-validations.gemspec +281 -270
- data/lib/dm-validations.rb +36 -35
- data/lib/dm-validations/auto_validate.rb +10 -5
- data/lib/dm-validations/contextual_validators.rb +41 -4
- data/lib/dm-validations/formats/email.rb +12 -8
- data/lib/dm-validations/validation_errors.rb +4 -0
- data/lib/dm-validations/validators/acceptance_validator.rb +4 -0
- data/lib/dm-validations/validators/format_validator.rb +3 -3
- data/lib/dm-validations/validators/generic_validator.rb +8 -20
- data/lib/dm-validations/validators/length_validator.rb +7 -1
- data/lib/dm-validations/validators/uniqueness_validator.rb +8 -2
- data/spec/fixtures/mittelschnauzer.rb +1 -0
- data/spec/integration/automatic_validation/inferred_boolean_properties_validation_spec.rb +20 -24
- data/spec/integration/automatic_validation/inferred_float_property_validation_spec.rb +11 -3
- data/spec/integration/automatic_validation/inferred_integer_properties_validation_spec.rb +9 -14
- data/spec/integration/automatic_validation/inferred_length_validation_spec.rb +9 -0
- data/spec/integration/automatic_validation/inferred_uniqueness_validation_spec.rb +48 -0
- data/spec/integration/automatic_validation/spec_helper.rb +2 -19
- data/spec/integration/format_validator/email_format_validator_spec.rb +15 -1
- data/spec/unit/generic_validator/optional_spec.rb +54 -0
- data/spec/unit/validation_errors/respond_to_spec.rb +15 -0
- data/tasks/spec.rake +0 -3
- metadata +69 -42
- data/.gitignore +0 -37
- data/tasks/ci.rake +0 -1
- data/tasks/local_gemfile.rake +0 -16
- data/tasks/metrics.rake +0 -36
data/lib/dm-validations.rb
CHANGED
@@ -1,43 +1,48 @@
|
|
1
1
|
require 'dm-core'
|
2
2
|
|
3
3
|
begin
|
4
|
-
|
4
|
+
# We need array for extract_options! which attr_accessors uses, at least in AS
|
5
|
+
# 2.3.3.
|
6
|
+
require 'active_support/core_ext/array'
|
5
7
|
require 'active_support/core_ext/class/attribute_accessors'
|
6
|
-
|
7
|
-
require '
|
8
|
-
|
9
|
-
class Object
|
10
|
-
# If receiver is callable, calls it and
|
11
|
-
# returns result. If not, just returns receiver
|
12
|
-
# itself
|
13
|
-
#
|
14
|
-
# @return [Object]
|
15
|
-
def try_call(*args)
|
16
|
-
if self.respond_to?(:call)
|
17
|
-
self.call(*args)
|
18
|
-
else
|
19
|
-
self
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
8
|
+
rescue LoadError
|
9
|
+
require 'extlib/class'
|
10
|
+
end
|
23
11
|
|
12
|
+
begin
|
13
|
+
require 'active_support/core_ext/object/blank'
|
24
14
|
rescue LoadError
|
15
|
+
require 'extlib/blank'
|
16
|
+
end
|
25
17
|
|
26
|
-
|
18
|
+
begin
|
19
|
+
require 'active_support/ordered_hash'
|
20
|
+
rescue LoadError
|
27
21
|
require 'extlib/dictionary'
|
28
|
-
require 'extlib/blank'
|
29
|
-
require 'extlib/try_dup'
|
30
|
-
require 'extlib/object'
|
31
22
|
|
32
|
-
module ActiveSupport
|
33
|
-
OrderedHash = Dictionary
|
23
|
+
module ::ActiveSupport
|
24
|
+
OrderedHash = ::Dictionary
|
34
25
|
end
|
26
|
+
end
|
35
27
|
|
28
|
+
class Object
|
29
|
+
# If receiver is callable, calls it and
|
30
|
+
# returns result. If not, just returns receiver
|
31
|
+
# itself
|
32
|
+
#
|
33
|
+
# @return [Object]
|
34
|
+
def try_call(*args)
|
35
|
+
if self.respond_to?(:call)
|
36
|
+
self.call(*args)
|
37
|
+
else
|
38
|
+
self
|
39
|
+
end
|
40
|
+
end
|
36
41
|
end
|
37
42
|
|
38
43
|
module DataMapper
|
39
44
|
class Property
|
40
|
-
def self.new(model, name, options = {}
|
45
|
+
def self.new(model, name, options = {})
|
41
46
|
property = super
|
42
47
|
property.model.auto_generate_validations(property)
|
43
48
|
|
@@ -77,16 +82,6 @@ module DataMapper
|
|
77
82
|
extend Chainable
|
78
83
|
|
79
84
|
def self.included(model)
|
80
|
-
model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
81
|
-
def self.create(attributes = {}, *args)
|
82
|
-
resource = new(attributes)
|
83
|
-
resource.save(*args)
|
84
|
-
resource
|
85
|
-
end
|
86
|
-
RUBY
|
87
|
-
|
88
|
-
# models that are non DM resources must get .validators
|
89
|
-
# and other methods, too
|
90
85
|
model.extend ClassMethods
|
91
86
|
end
|
92
87
|
|
@@ -181,6 +176,12 @@ module DataMapper
|
|
181
176
|
end
|
182
177
|
end
|
183
178
|
|
179
|
+
def create(attributes = {}, *args)
|
180
|
+
resource = new(attributes)
|
181
|
+
resource.save(*args)
|
182
|
+
resource
|
183
|
+
end
|
184
|
+
|
184
185
|
private
|
185
186
|
|
186
187
|
# Clean up the argument list and return a opts hash, including the
|
@@ -130,11 +130,16 @@ module DataMapper
|
|
130
130
|
end
|
131
131
|
|
132
132
|
def infer_length_validation_for(property, options)
|
133
|
-
return unless [ DataMapper::Property::String, DataMapper::Property::Text ].
|
133
|
+
return unless [ DataMapper::Property::String, DataMapper::Property::Text ].any? { |klass| property.kind_of?(klass) }
|
134
134
|
|
135
|
-
|
136
|
-
|
137
|
-
|
135
|
+
length = property.options.fetch(:length, DataMapper::Property::String::DEFAULT_LENGTH)
|
136
|
+
|
137
|
+
|
138
|
+
if length.is_a?(Range)
|
139
|
+
raise ArgumentError, "Infinity is no valid upper bound for a length range" if length.last == Infinity
|
140
|
+
options[:within] = length
|
141
|
+
else
|
142
|
+
options[:maximum] = length
|
138
143
|
end
|
139
144
|
|
140
145
|
validates_length_of property.name, options_with_message(options, property, :length)
|
@@ -170,7 +175,7 @@ module DataMapper
|
|
170
175
|
end
|
171
176
|
|
172
177
|
def infer_type_validation_for(property, options)
|
173
|
-
return if property.custom?
|
178
|
+
return if property.respond_to?(:custom?) && property.custom?
|
174
179
|
|
175
180
|
if property.kind_of?(Property::Numeric)
|
176
181
|
options[:gte] = property.min if property.min
|
@@ -43,7 +43,9 @@ module DataMapper
|
|
43
43
|
contexts.clear
|
44
44
|
end
|
45
45
|
|
46
|
-
# Execute all validators in the named context against the target
|
46
|
+
# Execute all validators in the named context against the target. Load
|
47
|
+
# together any properties that are designated lazy but are not yet loaded.
|
48
|
+
# Optionally only validate dirty properties.
|
47
49
|
#
|
48
50
|
# @param [Symbol]
|
49
51
|
# named_context the context we are validating against
|
@@ -54,9 +56,44 @@ module DataMapper
|
|
54
56
|
def execute(named_context, target)
|
55
57
|
target.errors.clear!
|
56
58
|
|
57
|
-
context(named_context).
|
58
|
-
|
59
|
-
|
59
|
+
runnable_validators = context(named_context).select{ |validator| validator.execute?(target) }
|
60
|
+
validators = runnable_validators.dup
|
61
|
+
|
62
|
+
# By default we start the list with the full set of runnable validators.
|
63
|
+
#
|
64
|
+
# In the case of a new Resource or regular ruby class instance,
|
65
|
+
# everything needs to be validated completely, and no eager-loading
|
66
|
+
# logic should apply.
|
67
|
+
#
|
68
|
+
# In the case of a DM::Resource that isn't new, we optimize:
|
69
|
+
#
|
70
|
+
# 1. Eager-load all lazy, not-yet-loaded properties that need
|
71
|
+
# validation, all at once.
|
72
|
+
#
|
73
|
+
# 2. Limit run validators to
|
74
|
+
# - those applied to dirty attributes only,
|
75
|
+
# - those that should always run (presence/absence)
|
76
|
+
# - those that don't reference any real properties (field-less
|
77
|
+
# block validators)
|
78
|
+
|
79
|
+
if target.kind_of?(DataMapper::Resource) and !target.new?
|
80
|
+
dirty_attrs = target.dirty_attributes.keys.map{ |p| p.name }
|
81
|
+
validators = runnable_validators.select{ |v| dirty_attrs.include?(v.field_name) }
|
82
|
+
|
83
|
+
# Load all lazy, not-yet-loaded properties that need validation, all at once.
|
84
|
+
fields_to_load = validators.map{ |v| target.class.properties[v.field_name] }.select{ |p| p.lazy? && !p.loaded?(target) }
|
85
|
+
target.__send__(:eager_load, fields_to_load)
|
86
|
+
|
87
|
+
# Finally include any validators that should always run or don't
|
88
|
+
# reference any real properties (field-less block vaildators).
|
89
|
+
validators |= runnable_validators.select do |v|
|
90
|
+
[ MethodValidator, PresenceValidator, AbsenceValidator ].any? do |klass|
|
91
|
+
v.kind_of?(klass)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
validators.map { |validator| validator.call(target) }.all?
|
60
97
|
end
|
61
98
|
|
62
99
|
end # module ContextualValidators
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# encoding:
|
1
|
+
# encoding: UTF-8
|
2
2
|
|
3
3
|
module DataMapper
|
4
4
|
module Validations
|
@@ -11,14 +11,19 @@ module DataMapper
|
|
11
11
|
)
|
12
12
|
end
|
13
13
|
|
14
|
-
# RFC2822 (No attribution reference available)
|
14
|
+
# Almost RFC2822 (No attribution reference available).
|
15
|
+
#
|
16
|
+
# This differs in that it does not allow local domains (test@localhost).
|
17
|
+
# 99% of the time you do not want to allow these email addresses
|
18
|
+
# in a public web application.
|
15
19
|
EmailAddress = begin
|
16
|
-
alpha = "a-zA-Z"
|
20
|
+
alpha = "a-zA-Z\\p{Lu}\\p{Ll}" # Alpha characters, changed from RFC2822 to include unicode chars
|
17
21
|
digit = "0-9"
|
18
22
|
atext = "[#{alpha}#{digit}\!\#\$\%\&\'\*+\/\=\?\^\_\`\{\|\}\~\-]"
|
19
|
-
dot_atom_text = "#{atext}+([.]#{atext}*)*
|
23
|
+
dot_atom_text = "#{atext}+([.]#{atext}*)+" # Last char changed from * to +
|
20
24
|
dot_atom = "#{dot_atom_text}"
|
21
|
-
|
25
|
+
no_ws_ctl = "\\x01-\\x08\\x11\\x12\\x14-\\x1f\\x7f"
|
26
|
+
qtext = "[^#{no_ws_ctl}\\x0d\\x22\\x5c]" # Non-whitespace, non-control character except for \ and "
|
22
27
|
text = "[\\x01-\\x09\\x11\\x12\\x14-\\x7f]"
|
23
28
|
quoted_pair = "(\\x5c#{text})"
|
24
29
|
qcontent = "(?:#{qtext}|#{quoted_pair})"
|
@@ -27,14 +32,13 @@ module DataMapper
|
|
27
32
|
word = "(?:#{atom}|#{quoted_string})"
|
28
33
|
obs_local_part = "#{word}([.]#{word})*"
|
29
34
|
local_part = "(?:#{dot_atom}|#{quoted_string}|#{obs_local_part})"
|
30
|
-
no_ws_ctl = "\\x01-\\x08\\x11\\x12\\x14-\\x1f\\x7f"
|
31
35
|
dtext = "[#{no_ws_ctl}\\x21-\\x5a\\x5e-\\x7e]"
|
32
36
|
dcontent = "(?:#{dtext}|#{quoted_pair})"
|
33
37
|
domain_literal = "\\[#{dcontent}+\\]"
|
34
|
-
obs_domain = "#{atom}([.]#{atom})*
|
38
|
+
obs_domain = "#{atom}([.]#{atom})+" # Last char changed from * to +
|
35
39
|
domain = "(?:#{dot_atom}|#{domain_literal}|#{obs_domain})"
|
36
40
|
addr_spec = "#{local_part}\@#{domain}"
|
37
|
-
pattern = /^#{addr_spec}$/
|
41
|
+
pattern = /^#{addr_spec}$/u
|
38
42
|
end
|
39
43
|
|
40
44
|
end # module Email
|
@@ -1,8 +1,8 @@
|
|
1
1
|
#require File.dirname(__FILE__) + '/formats/email'
|
2
2
|
|
3
3
|
require 'pathname'
|
4
|
-
require
|
5
|
-
require
|
4
|
+
require 'dm-validations/formats/email'
|
5
|
+
require 'dm-validations/formats/url'
|
6
6
|
|
7
7
|
module DataMapper
|
8
8
|
module Validations
|
@@ -70,7 +70,7 @@ module DataMapper
|
|
70
70
|
# @option :as<Format, Proc, Regexp> the pre-defined format, Proc or Regexp to validate against
|
71
71
|
# @option :with<Format, Proc, Regexp> an alias for :as
|
72
72
|
#
|
73
|
-
# :email_address (format is specified in DataMapper::Validations::Format::Email)
|
73
|
+
# :email_address (format is specified in DataMapper::Validations::Format::Email - note that unicode emails will *not* be matched under MRI1.8.7)
|
74
74
|
# :url (format is specified in DataMapper::Validations::Format::Url)
|
75
75
|
#
|
76
76
|
# @example [Usage]
|
@@ -90,30 +90,18 @@ module DataMapper
|
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
93
|
-
# Test the value to see if it is blank or nil, and if it is allowed
|
93
|
+
# Test the value to see if it is blank or nil, and if it is allowed.
|
94
|
+
# Note that allowing blank without explicitly denying nil allows nil
|
95
|
+
# values, since nil.blank? is true.
|
94
96
|
#
|
95
97
|
# @param <Object> value to test
|
96
98
|
# @return <Boolean> true if blank/nil is allowed, and the value is blank/nil
|
97
99
|
def optional?(value)
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
# Test if the value is nil and is allowed
|
104
|
-
#
|
105
|
-
# @param <Object> value to test
|
106
|
-
# @return <Boolean> true if nil is allowed and value is nil
|
107
|
-
def allow_nil?(value)
|
108
|
-
@options[:allow_nil] if value.nil?
|
109
|
-
end
|
110
|
-
|
111
|
-
# Test if the value is blank and is allowed
|
112
|
-
#
|
113
|
-
# @param <Object> value to test
|
114
|
-
# @return <Boolean> true if blank is allowed and value is blank
|
115
|
-
def allow_blank?(value)
|
116
|
-
@options[:allow_blank] if value.blank?
|
100
|
+
if value.nil?
|
101
|
+
@options[:allow_nil] || (@options[:allow_blank] && !@options.has_key?(:allow_nil))
|
102
|
+
elsif value.blank?
|
103
|
+
@options[:allow_blank]
|
104
|
+
end
|
117
105
|
end
|
118
106
|
|
119
107
|
# Returns true if validators are equal
|
@@ -94,7 +94,13 @@ module DataMapper
|
|
94
94
|
#
|
95
95
|
# @api private
|
96
96
|
def value_length(value)
|
97
|
-
value.to_str.
|
97
|
+
value.to_str.length
|
98
|
+
end
|
99
|
+
|
100
|
+
if RUBY_VERSION < '1.9'
|
101
|
+
def value_length(value)
|
102
|
+
value.to_str.scan(/./u).size
|
103
|
+
end
|
98
104
|
end
|
99
105
|
|
100
106
|
# Validate the value length is equal to the expected length
|
@@ -29,11 +29,17 @@ module DataMapper
|
|
29
29
|
return true if optional?(value)
|
30
30
|
|
31
31
|
opts = {
|
32
|
-
:fields => target.model.key,
|
32
|
+
:fields => target.model.key(target.repository.name),
|
33
33
|
field_name => value,
|
34
34
|
}
|
35
35
|
|
36
|
-
Array(@options[:scope]).each {
|
36
|
+
Array(@options[:scope]).each {|subject|
|
37
|
+
if target.respond_to?(subject)
|
38
|
+
opts[subject] = target.__send__(subject)
|
39
|
+
else
|
40
|
+
raise ArgumentError, "Could not find property to scope by: #{subject}. Note that :unique does not currently support arbitrarily named groups, for that you should use :unique_index with an explicit validates_uniqueness_of."
|
41
|
+
end
|
42
|
+
}
|
37
43
|
|
38
44
|
resource = DataMapper.repository(target.repository.name) { target.model.first(opts) }
|
39
45
|
|
@@ -6,57 +6,55 @@ describe "A model with a Boolean property" do
|
|
6
6
|
@model = HasNullableBoolean.new(:id => 1)
|
7
7
|
end
|
8
8
|
|
9
|
-
describe "assigned
|
9
|
+
describe "assigned a true" do
|
10
10
|
before :all do
|
11
|
-
@model.
|
11
|
+
@model.bool = true
|
12
12
|
end
|
13
13
|
|
14
14
|
it_should_behave_like "valid model"
|
15
15
|
end
|
16
16
|
|
17
|
-
describe "assigned
|
17
|
+
describe "assigned a false" do
|
18
18
|
before :all do
|
19
|
-
@model.
|
19
|
+
@model.bool = false
|
20
20
|
end
|
21
21
|
|
22
22
|
it_should_behave_like "valid model"
|
23
23
|
end
|
24
24
|
|
25
|
-
describe "assigned
|
25
|
+
describe "assigned a nil" do
|
26
26
|
before :all do
|
27
|
-
@model.
|
27
|
+
@model.bool = nil
|
28
28
|
end
|
29
29
|
|
30
30
|
it_should_behave_like "valid model"
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
|
35
|
-
|
36
34
|
describe "A model with a required Boolean property" do
|
37
35
|
before :all do
|
38
|
-
@model =
|
36
|
+
@model = HasRequiredBoolean.new(:id => 1)
|
39
37
|
end
|
40
38
|
|
41
|
-
describe "assigned
|
39
|
+
describe "assigned a true" do
|
42
40
|
before :all do
|
43
|
-
@model.
|
41
|
+
@model.bool = true
|
44
42
|
end
|
45
43
|
|
46
44
|
it_should_behave_like "valid model"
|
47
45
|
end
|
48
46
|
|
49
|
-
describe "assigned
|
47
|
+
describe "assigned a false" do
|
50
48
|
before :all do
|
51
|
-
@model.
|
49
|
+
@model.bool = false
|
52
50
|
end
|
53
51
|
|
54
52
|
it_should_behave_like "valid model"
|
55
53
|
end
|
56
54
|
|
57
|
-
describe "assigned
|
55
|
+
describe "assigned a nil" do
|
58
56
|
before :all do
|
59
|
-
@model.
|
57
|
+
@model.bool = nil
|
60
58
|
end
|
61
59
|
|
62
60
|
it_should_behave_like "invalid model"
|
@@ -67,32 +65,30 @@ describe "A model with a required Boolean property" do
|
|
67
65
|
end
|
68
66
|
end
|
69
67
|
|
70
|
-
|
71
|
-
|
72
68
|
describe "A model with a required paranoid Boolean property" do
|
73
69
|
before :all do
|
74
|
-
@model =
|
70
|
+
@model = HasRequiredParanoidBoolean.new(:id => 1)
|
75
71
|
end
|
76
72
|
|
77
|
-
describe "assigned
|
73
|
+
describe "assigned a true" do
|
78
74
|
before :all do
|
79
|
-
@model.
|
75
|
+
@model.bool = true
|
80
76
|
end
|
81
77
|
|
82
78
|
it_should_behave_like "valid model"
|
83
79
|
end
|
84
80
|
|
85
|
-
describe "assigned
|
81
|
+
describe "assigned a false" do
|
86
82
|
before :all do
|
87
|
-
@model.
|
83
|
+
@model.bool = false
|
88
84
|
end
|
89
85
|
|
90
86
|
it_should_behave_like "valid model"
|
91
87
|
end
|
92
88
|
|
93
|
-
describe "assigned
|
89
|
+
describe "assigned a nil" do
|
94
90
|
before :all do
|
95
|
-
@model.
|
91
|
+
@model.bool = nil
|
96
92
|
end
|
97
93
|
|
98
94
|
it_should_behave_like "invalid model"
|