dm-validations 1.1.0 → 1.2.0.rc1

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