dm-validations 0.9.11 → 0.10.0

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 (159) hide show
  1. data/{History.txt → History.rdoc} +10 -3
  2. data/LICENSE +1 -0
  3. data/Manifest.txt +103 -33
  4. data/README.rdoc +172 -0
  5. data/Rakefile +4 -5
  6. data/lib/dm-validations.rb +73 -75
  7. data/lib/dm-validations/auto_validate.rb +98 -56
  8. data/lib/dm-validations/contextual_validators.rb +37 -14
  9. data/lib/dm-validations/exceptions.rb +3 -0
  10. data/lib/dm-validations/formats/url.rb +3 -1
  11. data/lib/dm-validations/support/context.rb +54 -0
  12. data/lib/dm-validations/validation_errors.rb +51 -27
  13. data/lib/dm-validations/{absent_field_validator.rb → validators/absent_field_validator.rb} +2 -8
  14. data/lib/dm-validations/{acceptance_validator.rb → validators/acceptance_validator.rb} +7 -7
  15. data/lib/dm-validations/{block_validator.rb → validators/block_validator.rb} +0 -0
  16. data/lib/dm-validations/{confirmation_validator.rb → validators/confirmation_validator.rb} +7 -7
  17. data/lib/dm-validations/{format_validator.rb → validators/format_validator.rb} +10 -16
  18. data/lib/dm-validations/{generic_validator.rb → validators/generic_validator.rb} +28 -6
  19. data/lib/dm-validations/validators/length_validator.rb +214 -0
  20. data/lib/dm-validations/validators/method_validator.rb +68 -0
  21. data/lib/dm-validations/validators/numeric_validator.rb +171 -0
  22. data/lib/dm-validations/{primitive_validator.rb → validators/primitive_validator.rb} +2 -7
  23. data/lib/dm-validations/{required_field_validator.rb → validators/required_field_validator.rb} +0 -6
  24. data/lib/dm-validations/{uniqueness_validator.rb → validators/uniqueness_validator.rb} +9 -20
  25. data/lib/dm-validations/{within_validator.rb → validators/within_validator.rb} +4 -4
  26. data/lib/dm-validations/version.rb +1 -1
  27. data/spec/fixtures/barcode.rb +32 -0
  28. data/spec/fixtures/basketball_court.rb +58 -0
  29. data/spec/fixtures/basketball_player.rb +37 -0
  30. data/spec/fixtures/beta_tester_account.rb +33 -0
  31. data/spec/fixtures/bill_of_landing.rb +42 -0
  32. data/spec/fixtures/boat_dock.rb +26 -0
  33. data/spec/fixtures/city.rb +25 -0
  34. data/spec/fixtures/company.rb +95 -0
  35. data/spec/fixtures/corporate_world.rb +47 -0
  36. data/spec/fixtures/country.rb +25 -0
  37. data/spec/fixtures/currency.rb +42 -0
  38. data/spec/fixtures/ethernet_frame.rb +56 -0
  39. data/spec/fixtures/event.rb +44 -0
  40. data/spec/fixtures/g3_concert.rb +57 -0
  41. data/spec/fixtures/jabberwock.rb +27 -0
  42. data/spec/fixtures/kayak.rb +28 -0
  43. data/spec/fixtures/lernean_hydra.rb +39 -0
  44. data/spec/fixtures/mathematical_function.rb +34 -0
  45. data/spec/fixtures/memory_object.rb +30 -0
  46. data/spec/fixtures/mittelschnauzer.rb +35 -0
  47. data/spec/fixtures/motor_launch.rb +21 -0
  48. data/spec/fixtures/page.rb +32 -0
  49. data/spec/fixtures/phone_number.rb +28 -0
  50. data/spec/fixtures/pirogue.rb +28 -0
  51. data/spec/fixtures/programming_language.rb +83 -0
  52. data/spec/fixtures/reservation.rb +38 -0
  53. data/spec/fixtures/scm_operation.rb +67 -0
  54. data/spec/fixtures/sms_message.rb +22 -0
  55. data/spec/fixtures/udp_packet.rb +49 -0
  56. data/spec/integration/absent_field_validator/absent_field_validator_spec.rb +86 -0
  57. data/spec/integration/absent_field_validator/spec_helper.rb +7 -0
  58. data/spec/integration/acceptance_validator/acceptance_validator_spec.rb +196 -0
  59. data/spec/integration/acceptance_validator/spec_helper.rb +7 -0
  60. data/spec/integration/automatic_validation/custom_messages_for_inferred_validation_spec.rb +47 -0
  61. data/spec/integration/automatic_validation/disabling_inferred_validation_spec.rb +41 -0
  62. data/spec/integration/automatic_validation/inferred_boolean_properties_validation_spec.rb +104 -0
  63. data/spec/integration/automatic_validation/inferred_float_property_validation_spec.rb +35 -0
  64. data/spec/integration/automatic_validation/inferred_format_validation_spec.rb +33 -0
  65. data/spec/integration/automatic_validation/inferred_integer_properties_validation_spec.rb +73 -0
  66. data/spec/integration/automatic_validation/inferred_length_validation_spec.rb +121 -0
  67. data/spec/integration/automatic_validation/inferred_presence_validation_spec.rb +41 -0
  68. data/spec/integration/automatic_validation/inferred_primitive_validation_spec.rb +20 -0
  69. data/spec/integration/automatic_validation/inferred_within_validation_spec.rb +33 -0
  70. data/spec/integration/automatic_validation/spec_helper.rb +74 -0
  71. data/spec/integration/block_validator/block_validator_spec.rb +32 -0
  72. data/spec/integration/block_validator/spec_helper.rb +5 -0
  73. data/spec/integration/conditional_validation/if_condition_spec.rb +61 -0
  74. data/spec/integration/conditional_validation/spec_helper.rb +5 -0
  75. data/spec/integration/confirmation_validator/confirmation_validator_spec.rb +74 -0
  76. data/spec/integration/confirmation_validator/spec_helper.rb +5 -0
  77. data/spec/integration/datamapper_models/association_validation_spec.rb +23 -0
  78. data/spec/integration/datamapper_models/inheritance_spec.rb +78 -0
  79. data/spec/integration/duplicated_validations/duplicated_validations_spec.rb +22 -0
  80. data/spec/integration/duplicated_validations/spec_helper.rb +5 -0
  81. data/spec/integration/format_validator/email_format_validator_spec.rb +113 -0
  82. data/spec/integration/format_validator/format_validator_spec.rb +60 -0
  83. data/spec/integration/format_validator/regexp_validator_spec.rb +29 -0
  84. data/spec/integration/format_validator/spec_helper.rb +5 -0
  85. data/spec/integration/format_validator/url_format_validator_spec.rb +63 -0
  86. data/spec/integration/length_validator/default_value_spec.rb +12 -0
  87. data/spec/integration/length_validator/equality_spec.rb +79 -0
  88. data/spec/integration/length_validator/error_message_spec.rb +15 -18
  89. data/spec/integration/length_validator/maximum_spec.rb +40 -24
  90. data/spec/integration/length_validator/minimum_spec.rb +32 -21
  91. data/spec/integration/length_validator/range_spec.rb +50 -60
  92. data/spec/integration/length_validator/spec_helper.rb +7 -12
  93. data/spec/integration/method_validator/method_validator_spec.rb +239 -0
  94. data/spec/integration/method_validator/spec_helper.rb +5 -0
  95. data/spec/integration/numeric_validator/equality_with_float_type_spec.rb +61 -0
  96. data/spec/integration/numeric_validator/equality_with_integer_type_spec.rb +37 -0
  97. data/spec/integration/numeric_validator/float_type_spec.rb +42 -56
  98. data/spec/integration/numeric_validator/gt_with_float_type_spec.rb +35 -0
  99. data/spec/integration/numeric_validator/gte_with_float_type_spec.rb +35 -0
  100. data/spec/integration/numeric_validator/integer_only_true_spec.rb +3 -6
  101. data/spec/integration/numeric_validator/integer_type_spec.rb +8 -24
  102. data/spec/integration/numeric_validator/lt_with_float_type_spec.rb +35 -0
  103. data/spec/integration/numeric_validator/lte_with_float_type_spec.rb +35 -0
  104. data/spec/integration/numeric_validator/spec_helper.rb +5 -77
  105. data/spec/integration/primitive_validator/primitive_validator_spec.rb +90 -0
  106. data/spec/integration/primitive_validator/spec_helper.rb +5 -0
  107. data/spec/integration/pure_ruby_objects/plain_old_ruby_object_validation_spec.rb +118 -0
  108. data/spec/integration/required_field_validator/association_spec.rb +5 -8
  109. data/spec/integration/required_field_validator/boolean_type_value_spec.rb +4 -7
  110. data/spec/integration/required_field_validator/date_type_value_spec.rb +3 -6
  111. data/spec/integration/required_field_validator/datetime_type_value_spec.rb +3 -6
  112. data/spec/integration/required_field_validator/float_type_value_spec.rb +3 -6
  113. data/spec/integration/required_field_validator/integer_type_value_spec.rb +2 -5
  114. data/spec/integration/required_field_validator/plain_old_ruby_object_spec.rb +5 -8
  115. data/spec/integration/required_field_validator/shared_examples.rb +1 -1
  116. data/spec/integration/required_field_validator/spec_helper.rb +6 -67
  117. data/spec/integration/required_field_validator/string_type_value_spec.rb +2 -5
  118. data/spec/integration/required_field_validator/text_type_value_spec.rb +2 -5
  119. data/spec/integration/shared/default_validation_context.rb +13 -0
  120. data/spec/integration/shared/valid_and_invalid_model.rb +27 -0
  121. data/spec/integration/uniqueness_validator/spec_helper.rb +5 -0
  122. data/spec/integration/uniqueness_validator/uniqueness_validator_spec.rb +114 -0
  123. data/spec/integration/within_validator/spec_helper.rb +5 -0
  124. data/spec/integration/within_validator/within_validator_spec.rb +164 -0
  125. data/spec/public/resource_spec.rb +68 -0
  126. data/spec/spec.opts +1 -0
  127. data/spec/spec_helper.rb +12 -4
  128. data/spec/unit/contextual_validators/emptiness_spec.rb +50 -0
  129. data/spec/unit/contextual_validators/execution_spec.rb +55 -0
  130. data/spec/unit/contextual_validators/spec_helper.rb +37 -0
  131. data/spec/unit/generic_validator/equality_operator_spec.rb +26 -0
  132. data/spec/unit/validation_errors/adding_spec.rb +54 -0
  133. data/spec/unit/validation_errors/emptiness_spec.rb +38 -0
  134. data/spec/unit/validation_errors/enumerable_spec.rb +32 -0
  135. data/tasks/install.rb +1 -1
  136. data/tasks/spec.rb +4 -4
  137. metadata +118 -55
  138. data/README.txt +0 -72
  139. data/lib/dm-validations/custom_validator.rb +0 -72
  140. data/lib/dm-validations/length_validator.rb +0 -113
  141. data/lib/dm-validations/method_validator.rb +0 -68
  142. data/lib/dm-validations/numeric_validator.rb +0 -83
  143. data/spec/integration/absent_field_validator_spec.rb +0 -36
  144. data/spec/integration/acceptance_validator_spec.rb +0 -87
  145. data/spec/integration/auto_validate_spec.rb +0 -342
  146. data/spec/integration/block_validator_spec.rb +0 -30
  147. data/spec/integration/confirmation_validator_spec.rb +0 -105
  148. data/spec/integration/contextual_validators_spec.rb +0 -27
  149. data/spec/integration/custom_validator_spec.rb +0 -9
  150. data/spec/integration/format_validator_spec.rb +0 -193
  151. data/spec/integration/generic_validator_spec.rb +0 -17
  152. data/spec/integration/length_validator/valid_objects_spec.rb +0 -13
  153. data/spec/integration/method_validator_spec.rb +0 -58
  154. data/spec/integration/numeric_validator_spec.rb +0 -253
  155. data/spec/integration/primitive_validator_spec.rb +0 -30
  156. data/spec/integration/uniqueness_validator_spec.rb +0 -97
  157. data/spec/integration/validation_errors_spec.rb +0 -18
  158. data/spec/integration/validation_spec.rb +0 -404
  159. data/spec/integration/within_validator_spec.rb +0 -79
@@ -1,29 +1,36 @@
1
1
  module DataMapper
2
2
  class Property
3
3
  # for options_with_message
4
- PROPERTY_OPTIONS << :message << :messages << :set
4
+ OPTIONS << :message << :messages << :set
5
5
  end
6
6
 
7
7
  module Validate
8
8
  module AutoValidate
9
+ @disable_auto_validations = false
10
+
9
11
  # adds message for validator
10
12
  def options_with_message(base_options, property, validator_name)
11
13
  options = base_options.clone
12
- opts = property.options
13
- options[:message] = if opts[:messages]
14
- if opts[:messages].is_a?(Hash) and msg = opts[:messages][validator_name]
15
- msg
16
- else
17
- nil
18
- end
19
- elsif opts[:message]
20
- opts[:message]
21
- else
22
- nil
14
+ opts = property.options
15
+
16
+ if opts.key?(:messages)
17
+ options[:message] = opts[:messages][validator_name]
18
+ elsif opts.key?(:message)
19
+ options[:message] = opts[:message]
23
20
  end
21
+
24
22
  options
25
23
  end
26
24
 
25
+ attr_reader :disable_auto_validations
26
+
27
+ # disables generation of validations for
28
+ # duration of given block
29
+ def without_auto_validations(&block)
30
+ @disable_auto_validations = true
31
+ block.call
32
+ @disable_auto_validations = false
33
+ end
27
34
 
28
35
  ##
29
36
  # Auto-generate validations for a given property. This will only occur
@@ -37,8 +44,8 @@ module DataMapper
37
44
  # validates_presence_of validator to be automatically created on
38
45
  # the property
39
46
  #
40
- # :size => 20 or :length => 20
41
- # Setting the option :size or :length causes a validates_length_of
47
+ # :length => 20
48
+ # Setting the option :length causes a validates_length_of
42
49
  # validator to be automatically created on the property. If the
43
50
  # value is a Integer the validation will set :maximum => value if
44
51
  # the value is a Range the validation will set :within => value
@@ -76,78 +83,113 @@ module DataMapper
76
83
  # It is just shortcut if only one validation option is set
77
84
  #
78
85
  def auto_generate_validations(property)
79
- property.options[:auto_validation] = true unless property.options.has_key?(:auto_validation)
80
- return unless property.options[:auto_validation]
86
+ return if disabled_auto_validations? || skip_auto_validation_for?(property)
87
+
88
+ # all auto-validations (aside from presence) should skip
89
+ # validation when the value is nil
90
+ opts = { :allow_nil => true }
81
91
 
82
- # a serial property is allowed to be nil too, because the
83
- # value is set by the storage system
84
- opts = { :allow_nil => property.nullable? || property.serial? }
85
- opts[:context] = property.options[:validates] if property.options.has_key?(:validates)
92
+ if property.options.has_key?(:validates)
93
+ opts[:context] = property.options[:validates]
94
+ end
95
+
96
+ infer_presence_validation_for(property, opts.dup)
97
+ infer_length_validation_for(property, opts.dup)
98
+ infer_format_validation_for(property, opts.dup)
99
+ infer_uniqueness_validation_for(property, opts.dup)
100
+ infer_within_validation_for(property, opts.dup)
101
+ infer_numeric_validation_for(property, opts.dup)
102
+ end # auto_generate_validations
103
+
104
+ # Checks whether auto validations are currently
105
+ # disabled (see +disable_auto_validations+ method
106
+ # that takes a block)
107
+ #
108
+ # @return [TrueClass, FalseClass]
109
+ # true if auto validation is currently disabled
110
+ #
111
+ def disabled_auto_validations?
112
+ @disable_auto_validations || false
113
+ end
114
+ alias auto_validations_disabled? disabled_auto_validations?
86
115
 
87
- # presence
88
- unless opts[:allow_nil]
116
+
117
+ # Checks whether or not property should be auto validated.
118
+ # It is the case for properties with :auto_validation option
119
+ # given and it's value evaluates to true
120
+ #
121
+ # @return [TrueClass, FalseClass]
122
+ # true for properties with :auto_validation option that has positive value
123
+ def skip_auto_validation_for?(property)
124
+ property.options.has_key?(:auto_validation) && !property.options[:auto_validation]
125
+ end
126
+
127
+ def infer_presence_validation_for(property, options)
128
+ unless allow_nil?(property)
89
129
  # validates_present property.name, opts
90
- validates_present property.name, options_with_message(opts, property, :presence)
130
+ validates_present property.name, options_with_message(options, property, :presence)
91
131
  end
132
+ end
92
133
 
93
- # length
94
- if property.type == String
95
- #len = property.length # XXX: maybe length should always return a Range, with the min defaulting to 1
96
- len = property.options.fetch(:length, property.options.fetch(:size, DataMapper::Property::DEFAULT_LENGTH))
97
- if len.is_a?(Range)
98
- opts[:within] = len
99
- else
100
- opts[:maximum] = len
134
+ def infer_length_validation_for(property, options)
135
+ if [ String, DataMapper::Types::Text ].include?(property.type)
136
+ case length = property.options.fetch(:length, DataMapper::Property::DEFAULT_LENGTH)
137
+ when Range then options[:within] = length
138
+ else options[:maximum] = length
101
139
  end
102
- # validates_length property.name, opts
103
- validates_length property.name, options_with_message(opts, property, :length)
140
+
141
+ validates_length property.name, options_with_message(options, property, :length)
104
142
  end
143
+ end
105
144
 
106
- # format
145
+ def infer_format_validation_for(property, options)
107
146
  if property.options.has_key?(:format)
108
- opts[:with] = property.options[:format]
109
- # validates_format property.name, opts
110
- validates_format property.name, options_with_message(opts, property, :format)
147
+ options[:with] = property.options[:format]
148
+ validates_format property.name, options_with_message(options, property, :format)
111
149
  end
150
+ end
112
151
 
113
- # uniqueness validator
152
+ def infer_uniqueness_validation_for(property, options)
114
153
  if property.options.has_key?(:unique)
115
154
  value = property.options[:unique]
116
155
  if value.is_a?(Array) || value.is_a?(Symbol)
117
- # validates_is_unique property.name, :scope => Array(value)
118
156
  validates_is_unique property.name, options_with_message({:scope => Array(value)}, property, :is_unique)
119
157
  elsif value.is_a?(TrueClass)
120
- # validates_is_unique property.name
121
158
  validates_is_unique property.name, options_with_message({}, property, :is_unique)
122
159
  end
123
160
  end
161
+ end
124
162
 
125
- # within validator
126
- if property.options.has_key?(:set)
127
- validates_within property.name, options_with_message({:set => property.options[:set]}, property, :within)
128
- end
163
+ def infer_within_validation_for(property, options)
164
+ validates_within property.name, options_with_message({:set => property.options[:set]}, property, :within) if property.options.has_key?(:set)
165
+ end
166
+
167
+ def infer_numeric_validation_for(property, options)
168
+ options[:gte] = property.min if property.min
169
+ options[:lte] = property.max if property.max
129
170
 
130
- # numeric validator
131
171
  if Integer == property.type
132
- opts[:integer_only] = true
133
- # validates_is_number property.name, opts
134
- validates_is_number property.name, options_with_message(opts, property, :is_number)
172
+ options[:integer_only] = true
173
+
174
+ validates_is_number property.name, options_with_message(options, property, :is_number)
135
175
  elsif BigDecimal == property.type || Float == property.type
136
- opts[:precision] = property.precision
137
- opts[:scale] = property.scale
138
- # validates_is_number property.name, opts
139
- validates_is_number property.name, options_with_message(opts, property, :is_number)
176
+ options[:precision] = property.precision
177
+ options[:scale] = property.scale
178
+
179
+ validates_is_number property.name, options_with_message(options, property, :is_number)
140
180
  else
141
181
  # We only need this in the case we don't already
142
182
  # have a numeric validator, because otherwise
143
183
  # it will cause duplicate validation errors
144
- unless property.custom?
145
- # validates_is_primitive property.name, opts
146
- validates_is_primitive property.name, options_with_message(opts, property, :is_primitive)
147
- end
184
+ validates_is_primitive property.name, options_with_message(options, property, :is_primitive) unless property.custom?
148
185
  end
149
186
  end
150
187
 
188
+ private
189
+
190
+ def allow_nil?(property)
191
+ property.nullable? || property.serial?
192
+ end
151
193
  end # module AutoValidate
152
194
  end # module Validate
153
195
  end # module DataMapper
@@ -1,3 +1,5 @@
1
+ require "forwardable"
2
+
1
3
  module DataMapper
2
4
  module Validate
3
5
 
@@ -6,23 +8,38 @@ module DataMapper
6
8
  # @author Guy van den Berg
7
9
  # @since 0.9
8
10
  class ContextualValidators
11
+ extend Forwardable
12
+
13
+ #
14
+ # Delegators
15
+ #
16
+
17
+ def_delegators :@contexts, :empty?, :each
18
+ include Enumerable
19
+
20
+ attr_reader :contexts
21
+
22
+ def initialize
23
+ @contexts = {}
24
+ end
25
+
26
+ #
27
+ # API
28
+ #
9
29
 
10
30
  def dump
11
31
  contexts.each_pair do |key, context|
12
32
  puts "Key=#{key} Context: #{context}"
13
33
  end
14
34
  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 ||= {}
21
- end
35
+ alias_method :inspect, :dump
22
36
 
23
37
  # Return an array of validators for a named context
24
38
  #
25
- # @return <Array> An array of validators
39
+ # @param [String]
40
+ # Context name for which return validators
41
+ # @return [Array<DataMapper::Validate::GenericValidator>]
42
+ # An array of validators
26
43
  def context(name)
27
44
  contexts[name] ||= []
28
45
  end
@@ -35,18 +52,24 @@ module DataMapper
35
52
 
36
53
  # Execute all validators in the named context against the target
37
54
  #
38
- # @param <Symbol> named_context the context we are validating against
39
- # @param <Object> target the resource that we are validating
40
- # @return <Boolean> true if all are valid, otherwise false
55
+ # @param [Symbol]
56
+ # named_context the context we are validating against
57
+ # @param [Object]
58
+ # target the resource that we are validating
59
+ # @return [Boolean]
60
+ # true if all are valid, otherwise false
41
61
  def execute(named_context, target)
42
- raise(ArgumentError, 'invalid context specified') if !named_context || (contexts.length > 0 && !contexts[named_context])
62
+ raise(ArgumentError, "validation context #{named_context} doesn't seem to be defined. Known contexts are #{contexts.keys.inspect}") if !named_context || (contexts.length > 0 && !contexts[named_context])
43
63
  target.errors.clear!
44
64
  result = true
45
- context(named_context).each do |validator|
46
- next unless validator.execute?(target)
65
+ # note that all? and any? stop iteration on first negative or positive result,
66
+ # so we really have to use each here to make sure all validators are
67
+ # executed
68
+ context(named_context).select { |validator| validator.execute?(target) }.each do |validator|
47
69
  result = false unless validator.call(target)
48
70
  end
49
71
 
72
+
50
73
  result
51
74
  end
52
75
 
@@ -0,0 +1,3 @@
1
+ module DataMapper
2
+ class ValidationError < StandardError; end
3
+ end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module DataMapper
2
4
  module Validate
3
5
  module Format
@@ -11,7 +13,7 @@ module DataMapper
11
13
 
12
14
  Url = begin
13
15
  # Regex from http://www.igvita.com/2006/09/07/validating-url-in-ruby-on-rails/
14
- /(^$)|(^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?$)/ix
16
+ /(^$)|(^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}((\:[0-9]{1,5})?\/?.*)?$)/ix
15
17
  end
16
18
 
17
19
  end # module Url
@@ -0,0 +1,54 @@
1
+ module DataMapper
2
+ module Validate
3
+
4
+ # Module with validation context functionality.
5
+ #
6
+ # Contexts are implemented using a simple array based
7
+ # stack that is thread local. The default context can be
8
+ # altered by overwriting default_validation_context or
9
+ # will default to :default
10
+ #
11
+ module Context
12
+
13
+ # TODO: document
14
+ # @api private
15
+ def default_validation_context
16
+ current_validation_context || :default
17
+ end
18
+
19
+ protected
20
+
21
+ # Pushes given context on top of context stack and yields
22
+ # given block, then pops the stack. During block execution
23
+ # contexts previously pushed onto the stack have no effect.
24
+ #
25
+ # @api private
26
+ def validation_context(context = default_validation_context)
27
+ validation_context_stack << context
28
+ begin
29
+ yield
30
+ ensure
31
+ validation_context_stack.pop
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ # Initializes (if necessary) and returns current scope stack
38
+ # @api private
39
+ def validation_context_stack
40
+ Thread.current[:dm_validations_context_stack] ||= []
41
+ end
42
+
43
+ # Returns the current validation context or nil if none has been pushed
44
+ # @api private
45
+ def current_validation_context
46
+ validation_context_stack.last
47
+ end
48
+
49
+ end
50
+
51
+ include Context
52
+
53
+ end
54
+ end
@@ -1,3 +1,5 @@
1
+ require 'extlib/dictionary'
2
+
1
3
  module DataMapper
2
4
  module Validate
3
5
 
@@ -10,27 +12,28 @@ module DataMapper
10
12
  include Enumerable
11
13
 
12
14
  @@default_error_messages = {
13
- :absent => '%s must be absent',
14
- :inclusion => '%s must be one of [%s]',
15
- :invalid => '%s has an invalid format',
16
- :confirmation => '%s does not match the confirmation',
17
- :accepted => "%s is not accepted",
18
- :nil => '%s must not be nil',
19
- :blank => '%s must not be blank',
20
- :length_between => '%s must be between %s and %s characters long',
21
- :too_long => '%s must be less than %s characters long',
22
- :too_short => '%s must be more than %s characters long',
23
- :wrong_length => '%s must be %s characters long',
24
- :taken => '%s is already taken',
25
- :not_a_number => '%s must be a number',
26
- :not_an_integer => '%s must be an integer',
27
- :greater_than => '%s must be greater than %s',
28
- :greater_than_or_equal_to => "%s must be greater than or equal to %s",
29
- :equal_to => "%s must be equal to %s",
30
- :less_than => '%s must be less than %s',
31
- :less_than_or_equal_to => "%s must be less than or equal to %s",
32
- :value_between => '%s must be between %s and %s',
33
- :primitive => '%s must be of type %s'
15
+ :absent => '%s must be absent',
16
+ :inclusion => '%s must be one of %s',
17
+ :invalid => '%s has an invalid format',
18
+ :confirmation => '%s does not match the confirmation',
19
+ :accepted => '%s is not accepted',
20
+ :nil => '%s must not be nil',
21
+ :blank => '%s must not be blank',
22
+ :length_between => '%s must be between %s and %s characters long',
23
+ :too_long => '%s must be at most %s characters long',
24
+ :too_short => '%s must be at least %s characters long',
25
+ :wrong_length => '%s must be %s characters long',
26
+ :taken => '%s is already taken',
27
+ :not_a_number => '%s must be a number',
28
+ :not_an_integer => '%s must be an integer',
29
+ :greater_than => '%s must be greater than %s',
30
+ :greater_than_or_equal_to => '%s must be greater than or equal to %s',
31
+ :equal_to => '%s must be equal to %s',
32
+ :not_equal_to => '%s must not be equal to %s',
33
+ :less_than => '%s must be less than %s',
34
+ :less_than_or_equal_to => '%s must be less than or equal to %s',
35
+ :value_between => '%s must be between %s and %s',
36
+ :primitive => '%s must be of type %s'
34
37
  }
35
38
 
36
39
  # Holds a hash with all the default error messages that can be replaced by your own copy or localizations.
@@ -41,6 +44,13 @@ module DataMapper
41
44
  @@default_error_messages[key] % [field, *values].flatten
42
45
  end
43
46
 
47
+ attr_reader :resource
48
+
49
+ def initialize(resource)
50
+ @resource = resource
51
+ @errors = Dictionary.new
52
+ end
53
+
44
54
  # Clear existing validation errors.
45
55
  def clear!
46
56
  errors.clear
@@ -49,9 +59,21 @@ module DataMapper
49
59
  # Add a validation error. Use the field_name :general if the errors does
50
60
  # not apply to a specific field of the Resource.
51
61
  #
52
- # @param <Symbol> field_name the name of the field that caused the error
53
- # @param <String> message the message to add
62
+ # @param [Symbol] field_name the name of the field that caused the error
63
+ # @param [String] message the message to add
54
64
  def add(field_name, message)
65
+ # see 6abe8fff in extlib, but don't enforce
66
+ # it unless Edge version is installed
67
+ if message.respond_to?(:try_call)
68
+
69
+ # DM resource
70
+ message = if resource.respond_to?(:model) && resource.model.respond_to?(:properties)
71
+ message.try_call(resource, resource.model.properties[field_name])
72
+ else
73
+ # pure Ruby object
74
+ message.try_call(resource)
75
+ end
76
+ end
55
77
  (errors[field_name] ||= []) << message
56
78
  end
57
79
 
@@ -64,10 +86,12 @@ module DataMapper
64
86
 
65
87
  # Return validation errors for a particular field_name.
66
88
  #
67
- # @param <Symbol> field_name the name of the field you want an error for
89
+ # @param [Symbol] field_name the name of the field you want an error for
90
+ # @return [Array<DataMapper::Validate::Error>]
91
+ # array of validation errors or empty array, if there are no errors on given field
68
92
  def on(field_name)
69
93
  errors_for_field = errors[field_name]
70
- errors_for_field.blank? ? nil : errors_for_field
94
+ errors_for_field.blank? ? nil : errors_for_field.uniq
71
95
  end
72
96
 
73
97
  def each
@@ -78,7 +102,7 @@ module DataMapper
78
102
  end
79
103
 
80
104
  def empty?
81
- entries.empty?
105
+ @errors.empty?
82
106
  end
83
107
 
84
108
  def method_missing(meth, *args, &block)
@@ -87,7 +111,7 @@ module DataMapper
87
111
 
88
112
  private
89
113
  def errors
90
- @errors ||= {}
114
+ @errors
91
115
  end
92
116
 
93
117
  end # class ValidationErrors