dm-validations 0.9.11 → 0.10.0

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