ardm-validations 1.2.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 (153) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +35 -0
  3. data/.travis.yml +11 -0
  4. data/Gemfile +51 -0
  5. data/LICENSE +21 -0
  6. data/README.rdoc +122 -0
  7. data/Rakefile +4 -0
  8. data/ardm-validations.gemspec +28 -0
  9. data/lib/ardm-validations.rb +1 -0
  10. data/lib/dm-validations.rb +169 -0
  11. data/lib/dm-validations/auto_validate.rb +252 -0
  12. data/lib/dm-validations/context.rb +66 -0
  13. data/lib/dm-validations/contextual_validators.rb +220 -0
  14. data/lib/dm-validations/exceptions.rb +5 -0
  15. data/lib/dm-validations/formats/email.rb +65 -0
  16. data/lib/dm-validations/formats/url.rb +27 -0
  17. data/lib/dm-validations/support/object.rb +18 -0
  18. data/lib/dm-validations/support/ordered_hash.rb +434 -0
  19. data/lib/dm-validations/validation_errors.rb +137 -0
  20. data/lib/dm-validations/validators/absent_field_validator.rb +58 -0
  21. data/lib/dm-validations/validators/acceptance_validator.rb +79 -0
  22. data/lib/dm-validations/validators/block_validator.rb +61 -0
  23. data/lib/dm-validations/validators/confirmation_validator.rb +92 -0
  24. data/lib/dm-validations/validators/format_validator.rb +124 -0
  25. data/lib/dm-validations/validators/generic_validator.rb +184 -0
  26. data/lib/dm-validations/validators/length_validator.rb +249 -0
  27. data/lib/dm-validations/validators/method_validator.rb +64 -0
  28. data/lib/dm-validations/validators/numeric_validator.rb +182 -0
  29. data/lib/dm-validations/validators/primitive_validator.rb +58 -0
  30. data/lib/dm-validations/validators/required_field_validator.rb +83 -0
  31. data/lib/dm-validations/validators/uniqueness_validator.rb +67 -0
  32. data/lib/dm-validations/validators/within_validator.rb +74 -0
  33. data/lib/dm-validations/version.rb +5 -0
  34. data/spec/fixtures/barcode.rb +40 -0
  35. data/spec/fixtures/basketball_court.rb +58 -0
  36. data/spec/fixtures/basketball_player.rb +34 -0
  37. data/spec/fixtures/beta_tester_account.rb +33 -0
  38. data/spec/fixtures/bill_of_landing.rb +47 -0
  39. data/spec/fixtures/boat_dock.rb +26 -0
  40. data/spec/fixtures/city.rb +24 -0
  41. data/spec/fixtures/company.rb +93 -0
  42. data/spec/fixtures/corporate_world.rb +39 -0
  43. data/spec/fixtures/country.rb +24 -0
  44. data/spec/fixtures/ethernet_frame.rb +56 -0
  45. data/spec/fixtures/event.rb +44 -0
  46. data/spec/fixtures/g3_concert.rb +57 -0
  47. data/spec/fixtures/jabberwock.rb +27 -0
  48. data/spec/fixtures/kayak.rb +28 -0
  49. data/spec/fixtures/lernean_hydra.rb +39 -0
  50. data/spec/fixtures/llama_spaceship.rb +15 -0
  51. data/spec/fixtures/mathematical_function.rb +34 -0
  52. data/spec/fixtures/memory_object.rb +30 -0
  53. data/spec/fixtures/mittelschnauzer.rb +39 -0
  54. data/spec/fixtures/motor_launch.rb +21 -0
  55. data/spec/fixtures/multibyte.rb +16 -0
  56. data/spec/fixtures/page.rb +32 -0
  57. data/spec/fixtures/phone_number.rb +28 -0
  58. data/spec/fixtures/pirogue.rb +28 -0
  59. data/spec/fixtures/programming_language.rb +83 -0
  60. data/spec/fixtures/reservation.rb +38 -0
  61. data/spec/fixtures/scm_operation.rb +56 -0
  62. data/spec/fixtures/sms_message.rb +22 -0
  63. data/spec/fixtures/udp_packet.rb +49 -0
  64. data/spec/integration/absent_field_validator/absent_field_validator_spec.rb +90 -0
  65. data/spec/integration/absent_field_validator/spec_helper.rb +7 -0
  66. data/spec/integration/acceptance_validator/acceptance_validator_spec.rb +196 -0
  67. data/spec/integration/acceptance_validator/spec_helper.rb +7 -0
  68. data/spec/integration/automatic_validation/custom_messages_for_inferred_validation_spec.rb +57 -0
  69. data/spec/integration/automatic_validation/disabling_inferred_validation_spec.rb +49 -0
  70. data/spec/integration/automatic_validation/inferred_boolean_properties_validation_spec.rb +100 -0
  71. data/spec/integration/automatic_validation/inferred_float_property_validation_spec.rb +45 -0
  72. data/spec/integration/automatic_validation/inferred_format_validation_spec.rb +35 -0
  73. data/spec/integration/automatic_validation/inferred_integer_properties_validation_spec.rb +70 -0
  74. data/spec/integration/automatic_validation/inferred_length_validation_spec.rb +142 -0
  75. data/spec/integration/automatic_validation/inferred_presence_validation_spec.rb +45 -0
  76. data/spec/integration/automatic_validation/inferred_primitive_validation_spec.rb +22 -0
  77. data/spec/integration/automatic_validation/inferred_uniqueness_validation_spec.rb +52 -0
  78. data/spec/integration/automatic_validation/inferred_within_validation_spec.rb +39 -0
  79. data/spec/integration/automatic_validation/spec_helper.rb +57 -0
  80. data/spec/integration/block_validator/block_validator_spec.rb +32 -0
  81. data/spec/integration/block_validator/spec_helper.rb +5 -0
  82. data/spec/integration/conditional_validation/if_condition_spec.rb +63 -0
  83. data/spec/integration/conditional_validation/spec_helper.rb +5 -0
  84. data/spec/integration/confirmation_validator/confirmation_validator_spec.rb +76 -0
  85. data/spec/integration/confirmation_validator/spec_helper.rb +5 -0
  86. data/spec/integration/datamapper_models/association_validation_spec.rb +29 -0
  87. data/spec/integration/datamapper_models/inheritance_spec.rb +82 -0
  88. data/spec/integration/dirty_attributes/dirty_attributes_spec.rb +13 -0
  89. data/spec/integration/duplicated_validations/duplicated_validations_spec.rb +24 -0
  90. data/spec/integration/duplicated_validations/spec_helper.rb +5 -0
  91. data/spec/integration/format_validator/email_format_validator_spec.rb +139 -0
  92. data/spec/integration/format_validator/format_validator_spec.rb +64 -0
  93. data/spec/integration/format_validator/regexp_validator_spec.rb +33 -0
  94. data/spec/integration/format_validator/spec_helper.rb +5 -0
  95. data/spec/integration/format_validator/url_format_validator_spec.rb +93 -0
  96. data/spec/integration/length_validator/default_value_spec.rb +14 -0
  97. data/spec/integration/length_validator/equality_spec.rb +87 -0
  98. data/spec/integration/length_validator/error_message_spec.rb +22 -0
  99. data/spec/integration/length_validator/maximum_spec.rb +49 -0
  100. data/spec/integration/length_validator/minimum_spec.rb +54 -0
  101. data/spec/integration/length_validator/range_spec.rb +87 -0
  102. data/spec/integration/length_validator/spec_helper.rb +7 -0
  103. data/spec/integration/method_validator/method_validator_spec.rb +241 -0
  104. data/spec/integration/method_validator/spec_helper.rb +5 -0
  105. data/spec/integration/numeric_validator/equality_with_float_type_spec.rb +65 -0
  106. data/spec/integration/numeric_validator/equality_with_integer_type_spec.rb +41 -0
  107. data/spec/integration/numeric_validator/float_type_spec.rb +90 -0
  108. data/spec/integration/numeric_validator/gt_with_float_type_spec.rb +37 -0
  109. data/spec/integration/numeric_validator/gte_with_float_type_spec.rb +37 -0
  110. data/spec/integration/numeric_validator/integer_only_true_spec.rb +91 -0
  111. data/spec/integration/numeric_validator/integer_type_spec.rb +86 -0
  112. data/spec/integration/numeric_validator/lt_with_float_type_spec.rb +37 -0
  113. data/spec/integration/numeric_validator/lte_with_float_type_spec.rb +37 -0
  114. data/spec/integration/numeric_validator/spec_helper.rb +5 -0
  115. data/spec/integration/primitive_validator/primitive_validator_spec.rb +92 -0
  116. data/spec/integration/primitive_validator/spec_helper.rb +5 -0
  117. data/spec/integration/pure_ruby_objects/plain_old_ruby_object_validation_spec.rb +118 -0
  118. data/spec/integration/required_field_validator/association_spec.rb +72 -0
  119. data/spec/integration/required_field_validator/boolean_type_value_spec.rb +155 -0
  120. data/spec/integration/required_field_validator/date_type_value_spec.rb +127 -0
  121. data/spec/integration/required_field_validator/datetime_type_value_spec.rb +127 -0
  122. data/spec/integration/required_field_validator/float_type_value_spec.rb +131 -0
  123. data/spec/integration/required_field_validator/integer_type_value_spec.rb +99 -0
  124. data/spec/integration/required_field_validator/plain_old_ruby_object_spec.rb +35 -0
  125. data/spec/integration/required_field_validator/shared_examples.rb +26 -0
  126. data/spec/integration/required_field_validator/spec_helper.rb +7 -0
  127. data/spec/integration/required_field_validator/string_type_value_spec.rb +167 -0
  128. data/spec/integration/required_field_validator/text_type_value_spec.rb +49 -0
  129. data/spec/integration/shared/default_validation_context.rb +13 -0
  130. data/spec/integration/shared/valid_and_invalid_model.rb +35 -0
  131. data/spec/integration/uniqueness_validator/spec_helper.rb +5 -0
  132. data/spec/integration/uniqueness_validator/uniqueness_validator_spec.rb +116 -0
  133. data/spec/integration/within_validator/spec_helper.rb +5 -0
  134. data/spec/integration/within_validator/within_validator_spec.rb +168 -0
  135. data/spec/public/resource_spec.rb +105 -0
  136. data/spec/rcov.opts +6 -0
  137. data/spec/spec.opts +4 -0
  138. data/spec/spec_helper.rb +29 -0
  139. data/spec/unit/contextual_validators/emptiness_spec.rb +50 -0
  140. data/spec/unit/contextual_validators/execution_spec.rb +48 -0
  141. data/spec/unit/contextual_validators/spec_helper.rb +37 -0
  142. data/spec/unit/generic_validator/equality_operator_spec.rb +26 -0
  143. data/spec/unit/generic_validator/optional_spec.rb +54 -0
  144. data/spec/unit/validation_errors/adding_spec.rb +54 -0
  145. data/spec/unit/validation_errors/emptiness_spec.rb +38 -0
  146. data/spec/unit/validation_errors/enumerable_spec.rb +32 -0
  147. data/spec/unit/validation_errors/reading_spec.rb +35 -0
  148. data/spec/unit/validation_errors/respond_to_spec.rb +15 -0
  149. data/spec/unit/validators/within_validator_spec.rb +23 -0
  150. data/tasks/spec.rake +38 -0
  151. data/tasks/yard.rake +9 -0
  152. data/tasks/yardstick.rake +19 -0
  153. metadata +256 -0
@@ -0,0 +1,252 @@
1
+ module DataMapper
2
+ # for options_with_message
3
+ Property.accept_options :message, :messages, :set, :validates, :auto_validation, :format
4
+
5
+ module Validations
6
+ module AutoValidations
7
+
8
+ module ModelExtension
9
+ # @api private
10
+ def property(*)
11
+ property = super
12
+ AutoValidations.generate_for_property(property)
13
+ # FIXME: explicit return needed for YARD to parse this properly
14
+ return property
15
+ end
16
+
17
+ Model.append_extensions self
18
+ end # module ModelExtension
19
+
20
+
21
+ # TODO: why are there 3 entry points to this ivar?
22
+ # #disable_auto_validations, #disabled_auto_validations?, #auto_validations_disabled?
23
+ attr_reader :disable_auto_validations
24
+
25
+ # Checks whether auto validations are currently
26
+ # disabled (see +disable_auto_validations+ method
27
+ # that takes a block)
28
+ #
29
+ # @return [TrueClass, FalseClass]
30
+ # true if auto validation is currently disabled
31
+ #
32
+ # @api semipublic
33
+ def disabled_auto_validations?
34
+ @disable_auto_validations || false
35
+ end
36
+
37
+ # TODO: deprecate all but one of these 3 variants
38
+ alias_method :auto_validations_disabled?, :disabled_auto_validations?
39
+
40
+ # disables generation of validations for
41
+ # duration of given block
42
+ #
43
+ # @api public
44
+ def without_auto_validations
45
+ previous, @disable_auto_validations = @disable_auto_validations, true
46
+ yield
47
+ ensure
48
+ @disable_auto_validations = previous
49
+ end
50
+
51
+ # Auto-generate validations for a given property. This will only occur
52
+ # if the option :auto_validation is either true or left undefined.
53
+ #
54
+ # Triggers that generate validator creation
55
+ #
56
+ # :required => true
57
+ # Setting the option :required to true causes a
58
+ # validates_presence_of validator to be automatically created on
59
+ # the property
60
+ #
61
+ # :length => 20
62
+ # Setting the option :length causes a validates_length_of
63
+ # validator to be automatically created on the property. If the
64
+ # value is a Integer the validation will set :maximum => value
65
+ # if the value is a Range the validation will set
66
+ # :within => value
67
+ #
68
+ # :format => :predefined / lambda / Proc
69
+ # Setting the :format option causes a validates_format_of
70
+ # validator to be automatically created on the property
71
+ #
72
+ # :set => ["foo", "bar", "baz"]
73
+ # Setting the :set option causes a validates_within
74
+ # validator to be automatically created on the property
75
+ #
76
+ # Integer type
77
+ # Using a Integer type causes a validates_numericality_of
78
+ # validator to be created for the property. integer_only
79
+ # is set to true
80
+ #
81
+ # BigDecimal or Float type
82
+ # Using a Integer type causes a validates_numericality_of
83
+ # validator to be created for the property. integer_only
84
+ # is set to false, and precision/scale match the property
85
+ #
86
+ #
87
+ # Messages
88
+ #
89
+ # :messages => {..}
90
+ # Setting :messages hash replaces standard error messages
91
+ # with custom ones. For instance:
92
+ # :messages => {:presence => "Field is required",
93
+ # :format => "Field has invalid format"}
94
+ # Hash keys are: :presence, :format, :length, :is_unique,
95
+ # :is_number, :is_primitive
96
+ #
97
+ # :message => "Some message"
98
+ # It is just shortcut if only one validation option is set
99
+ #
100
+ # @api private
101
+ def self.generate_for_property(property)
102
+ return if (property.model.disabled_auto_validations? ||
103
+ skip_auto_validation_for?(property))
104
+
105
+ # all auto-validations (aside from presence) should skip
106
+ # validation when the value is nil
107
+ opts = { :allow_nil => true }
108
+
109
+ if property.options.key?(:validates)
110
+ opts[:context] = property.options[:validates]
111
+ end
112
+
113
+ infer_presence_validation_for(property, opts.dup)
114
+ infer_length_validation_for(property, opts.dup)
115
+ infer_format_validation_for(property, opts.dup)
116
+ infer_uniqueness_validation_for(property, opts.dup)
117
+ infer_within_validation_for(property, opts.dup)
118
+ infer_type_validation_for(property, opts.dup)
119
+ end # generate_for_property
120
+
121
+ private
122
+
123
+ # Checks whether or not property should be auto validated.
124
+ # It is the case for properties with :auto_validation option
125
+ # given and it's value evaluates to true
126
+ #
127
+ # @return [TrueClass, FalseClass]
128
+ # true for properties with :auto_validation option that has
129
+ # positive value
130
+ #
131
+ # @api private
132
+ def self.skip_auto_validation_for?(property)
133
+ (property.options.key?(:auto_validation) &&
134
+ !property.options[:auto_validation])
135
+ end
136
+
137
+ # @api private
138
+ def self.infer_presence_validation_for(property, options)
139
+ return if skip_presence_validation?(property)
140
+
141
+ validation_options = options_with_message(options, property, :presence)
142
+ property.model.validates_presence_of property.name, validation_options
143
+ end
144
+
145
+ # @api private
146
+ def self.skip_presence_validation?(property)
147
+ property.allow_blank? || property.serial?
148
+ end
149
+
150
+ # @api private
151
+ def self.infer_length_validation_for(property, options)
152
+ return unless (property.kind_of?(DataMapper::Property::String) ||
153
+ property.kind_of?(DataMapper::Property::Text))
154
+
155
+ length = property.options.fetch(:length, DataMapper::Property::String::DEFAULT_LENGTH)
156
+
157
+
158
+ if length.is_a?(Range)
159
+ raise ArgumentError, "Infinity is no valid upper bound for a length range" if length.last == Infinity
160
+ options[:within] = length
161
+ else
162
+ options[:maximum] = length
163
+ end
164
+
165
+ validation_options = options_with_message(options, property, :length)
166
+ property.model.validates_length_of property.name, validation_options
167
+ end
168
+
169
+ # @api private
170
+ def self.infer_format_validation_for(property, options)
171
+ return unless property.options.key?(:format)
172
+
173
+ options[:with] = property.options[:format]
174
+
175
+ validation_options = options_with_message(options, property, :format)
176
+ property.model.validates_format_of property.name, validation_options
177
+ end
178
+
179
+ # @api private
180
+ def self.infer_uniqueness_validation_for(property, options)
181
+ return unless property.options.key?(:unique)
182
+
183
+ case value = property.options[:unique]
184
+ when Array, Symbol
185
+ options[:scope] = Array(value)
186
+
187
+ validation_options = options_with_message(options, property, :is_unique)
188
+ property.model.validates_uniqueness_of property.name, validation_options
189
+ when TrueClass
190
+ validation_options = options_with_message(options, property, :is_unique)
191
+ property.model.validates_uniqueness_of property.name, validation_options
192
+ end
193
+ end
194
+
195
+ # @api private
196
+ def self.infer_within_validation_for(property, options)
197
+ return unless property.options.key?(:set)
198
+
199
+ options[:set] = property.options[:set]
200
+
201
+ validation_options = options_with_message(options, property, :within)
202
+ property.model.validates_within property.name, validation_options
203
+ end
204
+
205
+ # @api private
206
+ def self.infer_type_validation_for(property, options)
207
+ return if property.respond_to?(:custom?) && property.custom?
208
+
209
+ if property.kind_of?(Property::Numeric)
210
+ options[:gte] = property.min if property.min
211
+ options[:lte] = property.max if property.max
212
+ end
213
+
214
+ if Integer == property.primitive
215
+ options[:integer_only] = true
216
+
217
+ validation_options = options_with_message(options, property, :is_number)
218
+ property.model.validates_numericality_of property.name, validation_options
219
+ elsif (BigDecimal == property.primitive ||
220
+ Float == property.primitive)
221
+ options[:precision] = property.precision
222
+ options[:scale] = property.scale
223
+
224
+ validation_options = options_with_message(options, property, :is_number)
225
+ property.model.validates_numericality_of property.name, validation_options
226
+ else
227
+ # We only need this in the case we don't already
228
+ # have a numeric validator, because otherwise
229
+ # it will cause duplicate validation errors
230
+ validation_options = options_with_message(options, property, :is_primitive)
231
+ property.model.validates_primitive_type_of property.name, validation_options
232
+ end
233
+ end
234
+
235
+ # adds message for validator
236
+ #
237
+ # @api private
238
+ def self.options_with_message(base_options, property, validator_name)
239
+ options = base_options.clone
240
+ opts = property.options
241
+
242
+ if opts.key?(:messages)
243
+ options[:message] = opts[:messages][validator_name]
244
+ elsif opts.key?(:message)
245
+ options[:message] = opts[:message]
246
+ end
247
+
248
+ options
249
+ end
250
+ end # module AutoValidations
251
+ end # module Validations
252
+ end # module DataMapper
@@ -0,0 +1,66 @@
1
+ module DataMapper
2
+ module Validations
3
+ # Module with validation context functionality.
4
+ #
5
+ # Contexts are implemented using a thread-local array-based stack.
6
+ #
7
+ module Context
8
+ # Execute a block of code within a specific validation context
9
+ #
10
+ # @param [Symbol] context
11
+ # the context to execute the block of code within
12
+ #
13
+ # @api semipublic
14
+ def self.in_context(context)
15
+ stack << context
16
+ yield
17
+ ensure
18
+ stack.pop
19
+ end
20
+
21
+ # Get the current validation context or nil (if no context is on the stack).
22
+ #
23
+ # @return [Symbol, NilClass]
24
+ # The current validation context (for the current thread),
25
+ # or nil if no current context is on the stack
26
+ def self.current
27
+ stack.last
28
+ end
29
+
30
+ # Are there any contexts on the stack?
31
+ #
32
+ # @return [Boolean]
33
+ # true/false whether there are any contexts on the context stack
34
+ #
35
+ # @api semipublic
36
+ def self.any?(&block)
37
+ stack.any?(&block)
38
+ end
39
+
40
+ # The (thread-local) validation context stack
41
+ # This allows object graphs to be saved within potentially nested contexts
42
+ # without having to pass the validation context throughout
43
+ #
44
+ # @api private
45
+ def self.stack
46
+ Thread.current[:dm_validations_context_stack] ||= []
47
+ end
48
+
49
+ # The default validation context for this Resource.
50
+ # This Resource's default context can be overridden by implementing
51
+ # #default_validation_context
52
+ #
53
+ # @return [Symbol]
54
+ # the current validation context from the context stack
55
+ # (if valid for this model), or :default
56
+ #
57
+ # @api semipublic
58
+ def default_validation_context
59
+ model.validators.current_context || :default
60
+ end
61
+
62
+ end # module Context
63
+
64
+ include Context
65
+ end # module Validations
66
+ end # module DataMapper
@@ -0,0 +1,220 @@
1
+ require 'forwardable'
2
+
3
+ module DataMapper
4
+ module Validations
5
+ #
6
+ # @author Guy van den Berg
7
+ # @since 0.9
8
+ class ContextualValidators
9
+ extend Forwardable
10
+
11
+ #
12
+ # Delegators
13
+ #
14
+
15
+ def_delegators :@contexts, :empty?, :each
16
+ def_delegators :@attributes, :[]
17
+ include Enumerable
18
+
19
+ attr_reader :contexts, :attributes
20
+
21
+ def initialize(model = nil)
22
+ @model = model
23
+ @contexts = {}
24
+ @attributes = {}
25
+ end
26
+
27
+ #
28
+ # API
29
+ #
30
+
31
+ # Return an array of validators for a named context
32
+ #
33
+ # @param [String]
34
+ # Context name for which to return validators
35
+ # @return [Array<DataMapper::Validations::GenericValidator>]
36
+ # An array of validators bound to the given context
37
+ def context(name)
38
+ contexts[name] ||= OrderedSet.new
39
+ end
40
+
41
+ # Return an array of validators for a named property
42
+ #
43
+ # @param [Symbol]
44
+ # Property name for which to return validators
45
+ # @return [Array<DataMapper::Validations::GenericValidator>]
46
+ # An array of validators bound to the given property
47
+ def attribute(name)
48
+ attributes[name] ||= OrderedSet.new
49
+ end
50
+
51
+ # Clear all named context validators off of the resource
52
+ #
53
+ def clear!
54
+ contexts.clear
55
+ attributes.clear
56
+ end
57
+
58
+ # Create a new validator of the given klazz and push it onto the
59
+ # requested context for each of the attributes in +attributes+
60
+ #
61
+ # @param [DataMapper::Validations::GenericValidator] validator_class
62
+ # Validator class, example: DataMapper::Validations::LengthValidator
63
+ #
64
+ # @param [Array<Symbol>] attributes
65
+ # Attribute names given to validation macro, example:
66
+ # [:first_name, :last_name] in validates_presence_of :first_name, :last_name
67
+ #
68
+ # @param [Hash] options
69
+ # Options supplied to validation macro, example:
70
+ # {:context=>:default, :maximum=>50, :allow_nil=>true, :message=>nil}
71
+ #
72
+ # @option [Symbol] :context
73
+ # the context in which the new validator should be run
74
+ # @option [Boolean] :allow_nil
75
+ # whether or not the new validator should allow nil values
76
+ # @option [Boolean] :message
77
+ # the error message the new validator will provide on validation failure
78
+ def add(validator_class, *attributes)
79
+ options = attributes.last.kind_of?(Hash) ? attributes.pop.dup : {}
80
+ normalize_options(options)
81
+ validator_options = DataMapper::Ext::Hash.except(options, :context)
82
+
83
+ attributes.each do |attribute|
84
+ # TODO: is :context part of the Validator state (ie, intrinsic),
85
+ # or is it just membership in a collection?
86
+ validator = validator_class.new(attribute, validator_options)
87
+ attribute_validators = self.attribute(attribute)
88
+ attribute_validators << validator unless attribute_validators.include?(validator)
89
+
90
+ options[:context].each do |context|
91
+ context_validators = self.context(context)
92
+ next if context_validators.include?(validator)
93
+ context_validators << validator
94
+ # TODO: eliminate this, then eliminate the @model ivar entirely
95
+ Validations::ClassMethods.create_context_instance_methods(@model, context) if @model
96
+ end
97
+ end
98
+ end
99
+
100
+ # Clean up the argument list and return a opts hash, including the
101
+ # merging of any default opts. Set the context to default if none is
102
+ # provided. Also allow :context to be aliased to :on, :when & :group
103
+ #
104
+ # @param [Hash] options
105
+ # the options to be normalized
106
+ # @param [NilClass, Hash] defaults
107
+ # default keys/values to set on normalized options
108
+ #
109
+ # @return [Hash]
110
+ # the normalized options
111
+ #
112
+ # @api private
113
+ def normalize_options(options, defaults = nil)
114
+ context = [
115
+ options.delete(:group),
116
+ options.delete(:on),
117
+ options.delete(:when),
118
+ options.delete(:context)
119
+ ].compact.first
120
+
121
+ options[:context] = Array(context || :default)
122
+ options.update(defaults) unless defaults.nil?
123
+ options
124
+ end
125
+
126
+ # Returns the current validation context on the stack if valid for this model,
127
+ # nil if no contexts are defined for the model (and no contexts are on
128
+ # the validation stack), or :default if the current context is invalid for
129
+ # this model or no contexts have been defined for this model and
130
+ # no context is on the stack.
131
+ #
132
+ # @return [Symbol]
133
+ # the current validation context from the stack (if valid for this model),
134
+ # nil if no context is on the stack and no contexts are defined for this model,
135
+ # or :default if the context on the stack is invalid for this model or
136
+ # no context is on the stack and this model has at least one validation context
137
+ #
138
+ # @api private
139
+ #
140
+ # TODO: simplify the semantics of #current_context, #valid?
141
+ def current_context
142
+ context = Validations::Context.current
143
+ valid_context?(context) ? context : :default
144
+ end
145
+
146
+ # Test if the context is valid for the model
147
+ #
148
+ # @param [Symbol] context
149
+ # the context to test
150
+ #
151
+ # @return [Boolean]
152
+ # true if the context is valid for the model
153
+ #
154
+ # @api private
155
+ #
156
+ # TODO: investigate removing the `contexts.empty?` test here.
157
+ def valid_context?(context)
158
+ contexts.empty? || contexts.include?(context)
159
+ end
160
+
161
+ # Assert that the given context is valid for this model
162
+ #
163
+ # @param [Symbol] context
164
+ # the context to test
165
+ #
166
+ # @raise [InvalidContextError]
167
+ # raised if the context is not valid for this model
168
+ #
169
+ # @api private
170
+ #
171
+ # TODO: is this method actually needed?
172
+ def assert_valid(context)
173
+ unless valid_context?(context)
174
+ raise InvalidContextError, "#{context} is an invalid context, known contexts are #{contexts.keys.inspect}"
175
+ end
176
+ end
177
+
178
+ # Execute all validators in the named context against the target.
179
+ # Load together any properties that are designated lazy but are not
180
+ # yet loaded.
181
+ #
182
+ # @param [Symbol] named_context
183
+ # the context we are validating against
184
+ # @param [Object] target
185
+ # the resource that we are validating
186
+ # @return [Boolean]
187
+ # true if all are valid, otherwise false
188
+ def execute(named_context, target)
189
+ target.errors.clear!
190
+
191
+ available_validators = context(named_context)
192
+ executable_validators = available_validators.select { |v| v.execute?(target) }
193
+
194
+ # In the case of a new Resource or regular ruby class instance,
195
+ # everything needs to be validated completely, and no eager-loading
196
+ # logic should apply.
197
+ #
198
+ if target.kind_of?(DataMapper::Resource) && !target.new?
199
+ load_validated_properties(target, executable_validators)
200
+ end
201
+ executable_validators.map { |validator| validator.call(target) }.all?
202
+ end
203
+
204
+ # Load all lazy, not-yet-loaded properties that need validation,
205
+ # all at once.
206
+ def load_validated_properties(resource, validators)
207
+ properties = resource.model.properties
208
+
209
+ properties_to_load = validators.map { |validator|
210
+ properties[validator.field_name]
211
+ }.compact.select { |property|
212
+ property.lazy? && !property.loaded?(resource)
213
+ }
214
+
215
+ resource.__send__(:eager_load, properties_to_load)
216
+ end
217
+
218
+ end # module ContextualValidators
219
+ end # module Validations
220
+ end # module DataMapper