dm-validations 1.1.0 → 1.2.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.
Files changed (44) hide show
  1. data/Gemfile +8 -8
  2. data/Rakefile +2 -2
  3. data/VERSION +1 -1
  4. data/dm-validations.gemspec +29 -139
  5. data/lib/dm-validations.rb +51 -103
  6. data/lib/dm-validations/auto_validate.rb +104 -61
  7. data/lib/dm-validations/context.rb +66 -0
  8. data/lib/dm-validations/contextual_validators.rb +164 -53
  9. data/lib/dm-validations/formats/email.rb +41 -23
  10. data/lib/dm-validations/formats/url.rb +6 -1
  11. data/lib/dm-validations/support/object.rb +13 -0
  12. data/lib/dm-validations/validation_errors.rb +27 -19
  13. data/lib/dm-validations/validators/absent_field_validator.rb +11 -11
  14. data/lib/dm-validations/validators/acceptance_validator.rb +15 -13
  15. data/lib/dm-validations/validators/block_validator.rb +12 -11
  16. data/lib/dm-validations/validators/confirmation_validator.rb +23 -16
  17. data/lib/dm-validations/validators/format_validator.rb +45 -23
  18. data/lib/dm-validations/validators/generic_validator.rb +85 -38
  19. data/lib/dm-validations/validators/length_validator.rb +61 -26
  20. data/lib/dm-validations/validators/method_validator.rb +13 -17
  21. data/lib/dm-validations/validators/numeric_validator.rb +35 -35
  22. data/lib/dm-validations/validators/primitive_validator.rb +11 -12
  23. data/lib/dm-validations/validators/required_field_validator.rb +11 -13
  24. data/lib/dm-validations/validators/uniqueness_validator.rb +15 -14
  25. data/lib/dm-validations/validators/within_validator.rb +30 -12
  26. data/spec/fixtures/bill_of_landing.rb +5 -0
  27. data/spec/fixtures/llama_spaceship.rb +15 -0
  28. data/spec/integration/automatic_validation/custom_messages_for_inferred_validation_spec.rb +10 -0
  29. data/spec/integration/automatic_validation/disabling_inferred_validation_spec.rb +8 -0
  30. data/spec/integration/automatic_validation/inferred_length_validation_spec.rb +8 -0
  31. data/spec/integration/automatic_validation/inferred_uniqueness_validation_spec.rb +6 -2
  32. data/spec/integration/automatic_validation/inferred_within_validation_spec.rb +6 -0
  33. data/spec/integration/datamapper_models/association_validation_spec.rb +5 -2
  34. data/spec/integration/dirty_attributes/dirty_attributes_spec.rb +13 -0
  35. data/spec/integration/format_validator/email_format_validator_spec.rb +13 -5
  36. data/spec/integration/format_validator/url_format_validator_spec.rb +28 -2
  37. data/spec/integration/required_field_validator/association_spec.rb +5 -1
  38. data/spec/public/resource_spec.rb +2 -2
  39. data/spec/spec_helper.rb +1 -1
  40. data/spec/unit/contextual_validators/emptiness_spec.rb +10 -10
  41. data/spec/unit/contextual_validators/execution_spec.rb +4 -4
  42. data/spec/unit/validators/within_validator_spec.rb +23 -0
  43. metadata +81 -146
  44. data/lib/dm-validations/support/context.rb +0 -93
@@ -1,12 +1,11 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  module DataMapper
3
3
  module Validations
4
-
5
4
  # All validators extend this base class. Validators must:
6
5
  #
7
- # * Implement the initialize method to capture its parameters, also calling
8
- # super to have this parent class capture the optional, general :if and
9
- # :unless parameters.
6
+ # * Implement the initialize method to capture its parameters, also
7
+ # calling super to have this parent class capture the optional,
8
+ # general :if and :unless parameters.
10
9
  # * Implement the call method, returning true or false. The call method
11
10
  # provides the validation logic.
12
11
  #
@@ -17,16 +16,23 @@ module DataMapper
17
16
  attr_accessor :if_clause, :unless_clause
18
17
  attr_reader :field_name, :options, :humanized_field_name
19
18
 
20
- # Construct a validator. Capture the :if and :unless clauses when present.
19
+ # Construct a validator. Capture the :if and :unless clauses when
20
+ # present.
21
+ #
22
+ # @param [String, Symbol] field
23
+ # The property specified for validation.
24
+ #
25
+ # @option [Symbol, Proc] :if
26
+ # The name of a method or a Proc to call to determine if the
27
+ # validation should occur.
21
28
  #
22
- # @param field<String, Symbol> The property specified for validation
29
+ # @option [Symbol, Proc] :unless
30
+ # The name of a method or a Proc to call to determine if the
31
+ # validation should not occur.
23
32
  #
24
- # @option :if<Symbol, Proc> The name of a method or a Proc to call to
25
- # determine if the validation should occur.
26
- # @option :unless<Symbol, Proc> The name of a method or a Proc to call to
27
- # determine if the validation should not occur
28
- # All additional key/value pairs are passed through to the validator
29
- # that is sub-classing this GenericValidator
33
+ # @note
34
+ # All additional key/value pairs are passed through to the validator
35
+ # that is sub-classing this GenericValidator
30
36
  #
31
37
  def initialize(field_name, options = {})
32
38
  @field_name = field_name
@@ -36,26 +42,34 @@ module DataMapper
36
42
  @humanized_field_name = DataMapper::Inflector.humanize(@field_name)
37
43
  end
38
44
 
39
- # Add an error message to a target resource. If the error corresponds to a
40
- # specific field of the resource, add it to that field, otherwise add it
41
- # as a :general message.
45
+ # Add an error message to a target resource. If the error corresponds
46
+ # to a specific field of the resource, add it to that field,
47
+ # otherwise add it as a :general message.
42
48
  #
43
- # @param <Object> target the resource that has the error
44
- # @param <String> message the message to add
45
- # @param <Symbol> field_name the name of the field that caused the error
49
+ # @param [Object] target
50
+ # The resource that has the error.
46
51
  #
47
- # TODO - should the field_name for a general message be :default???
52
+ # @param [String] message
53
+ # The message to add.
54
+ #
55
+ # @param [Symbol] field_name
56
+ # The name of the field that caused the error.
48
57
  #
49
58
  def add_error(target, message, field_name = :general)
59
+ # TODO: should the field_name for a general message be :default???
50
60
  target.errors.add(field_name, message)
51
61
  end
52
62
 
53
- # Call the validator. "call" is used so the operation is BoundMethod and
54
- # Block compatible. This must be implemented in all concrete classes.
63
+ # Call the validator. "call" is used so the operation is BoundMethod
64
+ # and Block compatible. This must be implemented in all concrete
65
+ # classes.
66
+ #
67
+ # @param [Object] target
68
+ # The resource that the validator must be called against.
69
+ #
70
+ # @return [Boolean]
71
+ # true if valid, otherwise false.
55
72
  #
56
- # @param <Object> target the resource that the validator must be called
57
- # against
58
- # @return <Boolean> true if valid, otherwise false
59
73
  def call(target)
60
74
  raise NotImplementedError, "#{self.class}#call must be implemented"
61
75
  end
@@ -64,26 +78,37 @@ module DataMapper
64
78
  # target by evaluating the :if and :unless clauses
65
79
  # optionally passed while specifying any validator.
66
80
  #
67
- # @param <Object> target the resource that we check against
68
- # @return <Boolean> true if should be run, otherwise false
81
+ # @param [Object] target
82
+ # The resource that we check against.
83
+ #
84
+ # @return [Boolean]
85
+ # true if should be run, otherwise false.
86
+ #
87
+ # @api private
69
88
  def execute?(target)
70
89
  if unless_clause = self.unless_clause
71
- return !target.__send__(unless_clause) if unless_clause.kind_of?(Symbol)
72
- return !unless_clause.call(target) if unless_clause.respond_to?(:call)
90
+ !evaluate_conditional_clause(target, unless_clause)
91
+ elsif if_clause = self.if_clause
92
+ evaluate_conditional_clause(target, if_clause)
93
+ else
94
+ true
73
95
  end
96
+ end
74
97
 
75
- if if_clause = self.if_clause
76
- return target.__send__(if_clause) if if_clause.kind_of?(Symbol)
77
- return if_clause.call(target) if if_clause.respond_to?(:call)
98
+ # @api private
99
+ def evaluate_conditional_clause(target, clause)
100
+ if clause.kind_of?(Symbol)
101
+ target.__send__(clause)
102
+ elsif clause.respond_to?(:call)
103
+ clause.call(target)
78
104
  end
79
-
80
- true
81
105
  end
82
106
 
83
107
  # Set the default value for allow_nil and allow_blank
84
108
  #
85
109
  # @param [Boolean] default value
86
- # @return <undefined>
110
+ #
111
+ # @api private
87
112
  def set_optional_by_default(default = true)
88
113
  [ :allow_nil, :allow_blank ].each do |key|
89
114
  @options[key] = true unless options.key?(key)
@@ -94,11 +119,17 @@ module DataMapper
94
119
  # Note that allowing blank without explicitly denying nil allows nil
95
120
  # values, since nil.blank? is true.
96
121
  #
97
- # @param <Object> value to test
98
- # @return <Boolean> true if blank/nil is allowed, and the value is blank/nil
122
+ # @param [Object] value
123
+ # The value to test.
124
+ #
125
+ # @return [Boolean]
126
+ # true if blank/nil is allowed, and the value is blank/nil.
127
+ #
128
+ # @api private
99
129
  def optional?(value)
100
130
  if value.nil?
101
- @options[:allow_nil] || (@options[:allow_blank] && !@options.has_key?(:allow_nil))
131
+ @options[:allow_nil] ||
132
+ (@options[:allow_blank] && !@options.has_key?(:allow_nil))
102
133
  elsif DataMapper::Ext.blank?(value)
103
134
  @options[:allow_blank]
104
135
  end
@@ -124,7 +155,7 @@ module DataMapper
124
155
  self.field_name == other.field_name &&
125
156
  self.if_clause == other.if_clause &&
126
157
  self.unless_clause == other.unless_clause &&
127
- self.instance_variable_get(:@options) == other.instance_variable_get(:@options)
158
+ self.options == other.options
128
159
  end
129
160
 
130
161
  def inspect
@@ -132,6 +163,22 @@ module DataMapper
132
163
  end
133
164
 
134
165
  alias_method :to_s, :inspect
166
+
167
+ private
168
+
169
+ # Get the corresponding Resource property, if it exists.
170
+ #
171
+ # Note: DataMapper validations can be used on non-DataMapper resources.
172
+ # In such cases, the return value will be nil.
173
+ #
174
+ # @api private
175
+ def get_resource_property(resource, property_name)
176
+ model = resource.model if resource.respond_to?(:model)
177
+ repository = resource.repository if model
178
+ properties = model.properties(repository.name) if model
179
+ properties[property_name] if properties
180
+ end
181
+
135
182
  end # class GenericValidator
136
183
  end # module Validations
137
184
  end # module DataMapper
@@ -6,11 +6,10 @@ module DataMapper
6
6
  #
7
7
  # @param [Symbol] field_name
8
8
  # the name of the field to validate
9
+ #
9
10
  # @param [Hash] options
10
11
  # the validator options
11
12
  #
12
- # @return [undefined]
13
- #
14
13
  # @api semipublic
15
14
  def initialize(field_name, options)
16
15
  super
@@ -48,7 +47,6 @@ module DataMapper
48
47
  return true unless error_message = error_message_for(value)
49
48
 
50
49
  add_error(target, error_message, field_name)
51
-
52
50
  false
53
51
  end
54
52
 
@@ -114,7 +112,12 @@ module DataMapper
114
112
  # @api private
115
113
  def validate_equals(length)
116
114
  return if length == @equal
117
- ValidationErrors.default_error_message(:wrong_length, humanized_field_name, @equal)
115
+
116
+ ValidationErrors.default_error_message(
117
+ :wrong_length,
118
+ humanized_field_name,
119
+ @equal
120
+ )
118
121
  end
119
122
 
120
123
  # Validate the value length is within expected range
@@ -128,7 +131,13 @@ module DataMapper
128
131
  # @api private
129
132
  def validate_range(length)
130
133
  return if @range.include?(length)
131
- ValidationErrors.default_error_message(:length_between, humanized_field_name, @range.min, @range.max)
134
+
135
+ ValidationErrors.default_error_message(
136
+ :length_between,
137
+ humanized_field_name,
138
+ @range.min,
139
+ @range.max
140
+ )
132
141
  end
133
142
 
134
143
  # Validate the minimum expected value length
@@ -142,7 +151,12 @@ module DataMapper
142
151
  # @api private
143
152
  def validate_min(length)
144
153
  return if length >= @min
145
- ValidationErrors.default_error_message(:too_short, humanized_field_name, @min)
154
+
155
+ ValidationErrors.default_error_message(
156
+ :too_short,
157
+ humanized_field_name,
158
+ @min
159
+ )
146
160
  end
147
161
 
148
162
  # Validate the maximum expected value length
@@ -156,7 +170,12 @@ module DataMapper
156
170
  # @api private
157
171
  def validate_max(length)
158
172
  return if length <= @max
159
- ValidationErrors.default_error_message(:too_long, humanized_field_name, @max)
173
+
174
+ ValidationErrors.default_error_message(
175
+ :too_long,
176
+ humanized_field_name,
177
+ @max
178
+ )
160
179
  end
161
180
 
162
181
  end # class LengthValidator
@@ -168,22 +187,40 @@ module DataMapper
168
187
  # greater than or within a certain range (depending upon the options
169
188
  # you specify).
170
189
  #
171
- # @option :allow_nil<Boolean> true/false (default is true)
172
- # @option :allow_blank<Boolean> true/false (default is true)
173
- # @option :minimum ensures that the attribute's length is greater than
174
- # or equal to the supplied value
175
- # @option :min alias for :minimum
176
- # @option :maximum ensures the attribute's length is less than or equal
177
- # to the supplied value
178
- # @option :max alias for :maximum
179
- # @option :equals ensures the attribute's length is equal to the
180
- # supplied value
181
- # @option :is alias for :equals
182
- # @option :in<Range> given a Range, ensures that the attributes length is
183
- # include?'ed in the Range
184
- # @option :within<Range> alias for :in
185
- #
186
- # @example [Usage]
190
+ # @option [Boolean] :allow_nil (true)
191
+ # true or false.
192
+ #
193
+ # @option [Boolean] :allow_blank (true)
194
+ # true or false.
195
+ #
196
+ # @option [Boolean] :minimum
197
+ # Ensures that the attribute's length is greater than or equal to
198
+ # the supplied value.
199
+ #
200
+ # @option [Boolean] :min
201
+ # Alias for :minimum.
202
+ #
203
+ # @option [Boolean] :maximum
204
+ # Ensures the attribute's length is less than or equal to the
205
+ # supplied value.
206
+ #
207
+ # @option [Boolean] :max
208
+ # Alias for :maximum.
209
+ #
210
+ # @option [Boolean] :equals
211
+ # Ensures the attribute's length is equal to the supplied value.
212
+ #
213
+ # @option [Boolean] :is
214
+ # Alias for :equals.
215
+ #
216
+ # @option [Range] :in
217
+ # Given a Range, ensures that the attributes length is include?'ed
218
+ # in the Range.
219
+ #
220
+ # @option [Range] :within
221
+ # Alias for :in.
222
+ #
223
+ # @example Usage
187
224
  # require 'dm-validations'
188
225
  #
189
226
  # class Page
@@ -203,12 +240,10 @@ module DataMapper
203
240
  # # just_right is between 1 and 10 (inclusive of both 1 and 10)
204
241
  #
205
242
  def validates_length_of(*fields)
206
- opts = opts_from_validator_args(fields)
207
- add_validator_to_context(opts, fields, DataMapper::Validations::LengthValidator)
243
+ validators.add(LengthValidator, *fields)
208
244
  end
209
245
 
210
246
  deprecate :validates_length, :validates_length_of
211
-
212
247
  end # module ValidatesLength
213
248
  end # module Validations
214
249
  end # module DataMapper
@@ -1,8 +1,5 @@
1
1
  module DataMapper
2
2
  module Validations
3
-
4
- ##
5
- #
6
3
  # @author Guy van den Berg
7
4
  # @since 0.9
8
5
  class MethodValidator < GenericValidator
@@ -21,22 +18,22 @@ module DataMapper
21
18
  def ==(other)
22
19
  @options[:method] == other.instance_variable_get(:@options)[:method] && super
23
20
  end
21
+
24
22
  end # class MethodValidator
25
23
 
26
24
  module ValidatesWithMethod
27
-
28
- ##
29
- # Validate using method called on validated object. The method must to return
30
- # either true, or a pair of [false, error message string], and is specified
31
- # as a symbol passed with :method option.
25
+ # Validate using method called on validated object. The method must
26
+ # to return either true, or a pair of [false, error message string],
27
+ # and is specified as a symbol passed with :method option.
32
28
  #
33
- # This validator does support multiple fields being specified at a time,
34
- # but we encourage you to use it with one property/method at a time.
29
+ # This validator does support multiple fields being specified at a
30
+ # time, but we encourage you to use it with one property/method at a
31
+ # time.
35
32
  #
36
- # Real world experience shows that method validation is often useful when
37
- # attribute needs to be virtual and not a property name.
33
+ # Real world experience shows that method validation is often useful
34
+ # when attribute needs to be virtual and not a property name.
38
35
  #
39
- # @example [Usage]
36
+ # @example Usage
40
37
  # require 'dm-validations'
41
38
  #
42
39
  # class Page
@@ -44,7 +41,8 @@ module DataMapper
44
41
  #
45
42
  # property :zip_code, String
46
43
  #
47
- # validates_with_method :zip_code, :method => :in_the_right_location?
44
+ # validates_with_method :zip_code,
45
+ # :method => :in_the_right_location?
48
46
  #
49
47
  # def in_the_right_location?
50
48
  # if @zip_code == "94301"
@@ -59,10 +57,8 @@ module DataMapper
59
57
  # # wrong zip code" unless zip_code == "94301"
60
58
  # end
61
59
  def validates_with_method(*fields)
62
- opts = opts_from_validator_args(fields)
63
- add_validator_to_context(opts, fields, DataMapper::Validations::MethodValidator)
60
+ validators.add(MethodValidator, *fields)
64
61
  end
65
-
66
62
  end # module ValidatesWithMethod
67
63
  end # module Validations
68
64
  end # module DataMapper
@@ -1,8 +1,5 @@
1
1
  module DataMapper
2
2
  module Validations
3
-
4
- ##
5
- #
6
3
  # @author Guy van den Berg
7
4
  # @since 0.9
8
5
  class NumericalityValidator < GenericValidator
@@ -37,7 +34,8 @@ module DataMapper
37
34
 
38
35
  def value_as_string(value)
39
36
  case value
40
- when Float then value.to_d.to_s('F') # Avoid Scientific Notation in Float to_s
37
+ # Avoid Scientific Notation in Float to_s
38
+ when Float then value.to_d.to_s('F')
41
39
  when BigDecimal then value.to_s('F')
42
40
  else value.to_s
43
41
  end
@@ -72,7 +70,11 @@ module DataMapper
72
70
  comparison = value.send(cmp, expected)
73
71
  return if negated ? !comparison : comparison
74
72
 
75
- errors << ValidationErrors.default_error_message(error_message_name, field_name, expected)
73
+ errors << ValidationErrors.default_error_message(
74
+ error_message_name,
75
+ field_name,
76
+ expected
77
+ )
76
78
  end
77
79
 
78
80
  def validate_integer(value, errors)
@@ -124,59 +126,57 @@ module DataMapper
124
126
  def validate_ne(value, errors)
125
127
  validate_with_comparison(value, :==, options[:ne] || options[:not_equal_to], :not_equal_to, errors, true)
126
128
  end
129
+
127
130
  end # class NumericalityValidator
128
131
 
129
132
  module ValidatesNumericality
130
133
  extend Deprecate
131
134
 
132
- # Validate whether a field is numeric
135
+ # Validate whether a field is numeric.
133
136
  #
134
- # Options are:
137
+ # @option [Boolean] :allow_nil
138
+ # true if number can be nil, false if not.
135
139
  #
136
- # :allow_nil => true | false
137
- # true if number can be nil, false if not
140
+ # @option [Boolean] :allow_blank
141
+ # true if number can be blank, false if not.
138
142
  #
139
- # :allow_blank => true | false
140
- # true if number can be blank, false if not
141
- #
142
- # :message => "Error message for %s"
143
+ # @option [String] :message
143
144
  # Custom error message, also can be a callable object that takes
144
- # an object (for pure Ruby objects) or object and property (for DM resources)
145
+ # an object (for pure Ruby objects) or object and property
146
+ # (for DM resources).
145
147
  #
146
- # :precision => 2
147
- # Required precision of a value
148
+ # @option [Numeric] :precision
149
+ # Required precision of a value.
148
150
  #
149
- # :scale => 2
150
- # Required scale of a value
151
+ # @option [Numeric] :scale
152
+ # Required scale of a value.
151
153
  #
152
- # :gte => 5.75
153
- # 'Greater than or greater' requirement
154
+ # @option [Numeric] :gte
155
+ # 'Greater than or equal to' requirement.
154
156
  #
155
- # :lte => 5.75
156
- # 'Less than or greater' requirement
157
+ # @option [Numeric] :lte
158
+ # 'Less than or equal to' requirement.
157
159
  #
158
- # :lt => 5.75
159
- # 'Less than' requirement
160
+ # @option [Numeric] :lt
161
+ # 'Less than' requirement.
160
162
  #
161
- # :gt => 5.75
162
- # 'Greater than' requirement
163
+ # @option [Numeric] :gt
164
+ # 'Greater than' requirement.
163
165
  #
164
- # :eq => 5.75
165
- # 'Equal' requirement
166
+ # @option [Numeric] :eq
167
+ # 'Equal' requirement.
166
168
  #
167
- # :ne => 5.75
168
- # 'Not equal' requirement
169
+ # @option [Numeric] :ne
170
+ # 'Not equal' requirement.
169
171
  #
170
- # :integer_only => true
171
- # Use to restrict allowed values to integers
172
+ # @option [Boolean] :integer_only
173
+ # Use to restrict allowed values to integers.
172
174
  #
173
175
  def validates_numericality_of(*fields)
174
- opts = opts_from_validator_args(fields)
175
- add_validator_to_context(opts, fields, DataMapper::Validations::NumericalityValidator)
176
+ validators.add(NumericalityValidator, *fields)
176
177
  end
177
178
 
178
179
  deprecate :validates_is_number, :validates_numericality_of
179
-
180
180
  end # module ValidatesIsNumber
181
181
  end # module Validations
182
182
  end # module DataMapper