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.
- data/Gemfile +8 -8
- data/Rakefile +2 -2
- data/VERSION +1 -1
- data/dm-validations.gemspec +29 -139
- data/lib/dm-validations.rb +51 -103
- data/lib/dm-validations/auto_validate.rb +104 -61
- data/lib/dm-validations/context.rb +66 -0
- data/lib/dm-validations/contextual_validators.rb +164 -53
- data/lib/dm-validations/formats/email.rb +41 -23
- data/lib/dm-validations/formats/url.rb +6 -1
- data/lib/dm-validations/support/object.rb +13 -0
- data/lib/dm-validations/validation_errors.rb +27 -19
- data/lib/dm-validations/validators/absent_field_validator.rb +11 -11
- data/lib/dm-validations/validators/acceptance_validator.rb +15 -13
- data/lib/dm-validations/validators/block_validator.rb +12 -11
- data/lib/dm-validations/validators/confirmation_validator.rb +23 -16
- data/lib/dm-validations/validators/format_validator.rb +45 -23
- data/lib/dm-validations/validators/generic_validator.rb +85 -38
- data/lib/dm-validations/validators/length_validator.rb +61 -26
- data/lib/dm-validations/validators/method_validator.rb +13 -17
- data/lib/dm-validations/validators/numeric_validator.rb +35 -35
- data/lib/dm-validations/validators/primitive_validator.rb +11 -12
- data/lib/dm-validations/validators/required_field_validator.rb +11 -13
- data/lib/dm-validations/validators/uniqueness_validator.rb +15 -14
- data/lib/dm-validations/validators/within_validator.rb +30 -12
- data/spec/fixtures/bill_of_landing.rb +5 -0
- data/spec/fixtures/llama_spaceship.rb +15 -0
- data/spec/integration/automatic_validation/custom_messages_for_inferred_validation_spec.rb +10 -0
- data/spec/integration/automatic_validation/disabling_inferred_validation_spec.rb +8 -0
- data/spec/integration/automatic_validation/inferred_length_validation_spec.rb +8 -0
- data/spec/integration/automatic_validation/inferred_uniqueness_validation_spec.rb +6 -2
- data/spec/integration/automatic_validation/inferred_within_validation_spec.rb +6 -0
- data/spec/integration/datamapper_models/association_validation_spec.rb +5 -2
- data/spec/integration/dirty_attributes/dirty_attributes_spec.rb +13 -0
- data/spec/integration/format_validator/email_format_validator_spec.rb +13 -5
- data/spec/integration/format_validator/url_format_validator_spec.rb +28 -2
- data/spec/integration/required_field_validator/association_spec.rb +5 -1
- data/spec/public/resource_spec.rb +2 -2
- data/spec/spec_helper.rb +1 -1
- data/spec/unit/contextual_validators/emptiness_spec.rb +10 -10
- data/spec/unit/contextual_validators/execution_spec.rb +4 -4
- data/spec/unit/validators/within_validator_spec.rb +23 -0
- metadata +81 -146
- data/lib/dm-validations/support/context.rb +0 -93
@@ -1,38 +1,53 @@
|
|
1
1
|
module DataMapper
|
2
|
-
|
3
|
-
|
4
|
-
accept_options :message, :messages, :set, :validates, :auto_validation, :format
|
5
|
-
end
|
2
|
+
# for options_with_message
|
3
|
+
Property.accept_options :message, :messages, :set, :validates, :auto_validation, :format
|
6
4
|
|
7
5
|
module Validations
|
8
6
|
module AutoValidations
|
9
|
-
@disable_auto_validations = false
|
10
7
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
elsif opts.key?(:message)
|
19
|
-
options[:message] = opts[:message]
|
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
|
20
15
|
end
|
21
16
|
|
22
|
-
|
23
|
-
end
|
17
|
+
Model.append_extensions self
|
18
|
+
end # module ModelExtension
|
19
|
+
|
24
20
|
|
21
|
+
# TODO: why are there 3 entry points to this ivar?
|
22
|
+
# #disable_auto_validations, #disabled_auto_validations?, #auto_validations_disabled?
|
25
23
|
attr_reader :disable_auto_validations
|
26
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
|
+
|
27
40
|
# disables generation of validations for
|
28
41
|
# duration of given block
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
@disable_auto_validations =
|
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
|
33
49
|
end
|
34
50
|
|
35
|
-
##
|
36
51
|
# Auto-generate validations for a given property. This will only occur
|
37
52
|
# if the option :auto_validation is either true or left undefined.
|
38
53
|
#
|
@@ -46,8 +61,9 @@ module DataMapper
|
|
46
61
|
# :length => 20
|
47
62
|
# Setting the option :length causes a validates_length_of
|
48
63
|
# validator to be automatically created on the property. If the
|
49
|
-
# value is a Integer the validation will set :maximum => value
|
50
|
-
# the value is a Range the validation will set
|
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
|
51
67
|
#
|
52
68
|
# :format => :predefined / lambda / Proc
|
53
69
|
# Setting the :format option causes a validates_format_of
|
@@ -81,8 +97,10 @@ module DataMapper
|
|
81
97
|
# :message => "Some message"
|
82
98
|
# It is just shortcut if only one validation option is set
|
83
99
|
#
|
84
|
-
|
85
|
-
|
100
|
+
# @api private
|
101
|
+
def self.generate_for_property(property)
|
102
|
+
return if (property.model.disabled_auto_validations? ||
|
103
|
+
skip_auto_validation_for?(property))
|
86
104
|
|
87
105
|
# all auto-validations (aside from presence) should skip
|
88
106
|
# validation when the value is nil
|
@@ -98,39 +116,41 @@ module DataMapper
|
|
98
116
|
infer_uniqueness_validation_for(property, opts.dup)
|
99
117
|
infer_within_validation_for(property, opts.dup)
|
100
118
|
infer_type_validation_for(property, opts.dup)
|
101
|
-
end #
|
119
|
+
end # generate_for_property
|
102
120
|
|
103
|
-
|
104
|
-
# disabled (see +disable_auto_validations+ method
|
105
|
-
# that takes a block)
|
106
|
-
#
|
107
|
-
# @return [TrueClass, FalseClass]
|
108
|
-
# true if auto validation is currently disabled
|
109
|
-
#
|
110
|
-
def disabled_auto_validations?
|
111
|
-
@disable_auto_validations || false
|
112
|
-
end
|
113
|
-
|
114
|
-
alias_method :auto_validations_disabled?, :disabled_auto_validations?
|
121
|
+
private
|
115
122
|
|
116
123
|
# Checks whether or not property should be auto validated.
|
117
124
|
# It is the case for properties with :auto_validation option
|
118
125
|
# given and it's value evaluates to true
|
119
126
|
#
|
120
127
|
# @return [TrueClass, FalseClass]
|
121
|
-
# true for properties with :auto_validation option that has
|
122
|
-
|
123
|
-
|
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])
|
124
135
|
end
|
125
136
|
|
126
|
-
|
137
|
+
# @api private
|
138
|
+
def self.infer_presence_validation_for(property, options)
|
127
139
|
return if skip_presence_validation?(property)
|
128
140
|
|
129
|
-
|
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?
|
130
148
|
end
|
131
149
|
|
132
|
-
|
133
|
-
|
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))
|
134
154
|
|
135
155
|
length = property.options.fetch(:length, DataMapper::Property::String::DEFAULT_LENGTH)
|
136
156
|
|
@@ -142,39 +162,48 @@ module DataMapper
|
|
142
162
|
options[:maximum] = length
|
143
163
|
end
|
144
164
|
|
145
|
-
|
165
|
+
validation_options = options_with_message(options, property, :length)
|
166
|
+
property.model.validates_length_of property.name, validation_options
|
146
167
|
end
|
147
168
|
|
148
|
-
|
169
|
+
# @api private
|
170
|
+
def self.infer_format_validation_for(property, options)
|
149
171
|
return unless property.options.key?(:format)
|
150
172
|
|
151
173
|
options[:with] = property.options[:format]
|
152
174
|
|
153
|
-
|
175
|
+
validation_options = options_with_message(options, property, :format)
|
176
|
+
property.model.validates_format_of property.name, validation_options
|
154
177
|
end
|
155
178
|
|
156
|
-
|
179
|
+
# @api private
|
180
|
+
def self.infer_uniqueness_validation_for(property, options)
|
157
181
|
return unless property.options.key?(:unique)
|
158
182
|
|
159
183
|
case value = property.options[:unique]
|
160
184
|
when Array, Symbol
|
161
185
|
options[:scope] = Array(value)
|
162
186
|
|
163
|
-
|
187
|
+
validation_options = options_with_message(options, property, :is_unique)
|
188
|
+
property.model.validates_uniqueness_of property.name, validation_options
|
164
189
|
when TrueClass
|
165
|
-
|
190
|
+
validation_options = options_with_message(options, property, :is_unique)
|
191
|
+
property.model.validates_uniqueness_of property.name, validation_options
|
166
192
|
end
|
167
193
|
end
|
168
194
|
|
169
|
-
|
195
|
+
# @api private
|
196
|
+
def self.infer_within_validation_for(property, options)
|
170
197
|
return unless property.options.key?(:set)
|
171
198
|
|
172
199
|
options[:set] = property.options[:set]
|
173
200
|
|
174
|
-
|
201
|
+
validation_options = options_with_message(options, property, :within)
|
202
|
+
property.model.validates_within property.name, validation_options
|
175
203
|
end
|
176
204
|
|
177
|
-
|
205
|
+
# @api private
|
206
|
+
def self.infer_type_validation_for(property, options)
|
178
207
|
return if property.respond_to?(:custom?) && property.custom?
|
179
208
|
|
180
209
|
if property.kind_of?(Property::Numeric)
|
@@ -185,24 +214,38 @@ module DataMapper
|
|
185
214
|
if Integer == property.primitive
|
186
215
|
options[:integer_only] = true
|
187
216
|
|
188
|
-
|
189
|
-
|
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)
|
190
221
|
options[:precision] = property.precision
|
191
222
|
options[:scale] = property.scale
|
192
223
|
|
193
|
-
|
224
|
+
validation_options = options_with_message(options, property, :is_number)
|
225
|
+
property.model.validates_numericality_of property.name, validation_options
|
194
226
|
else
|
195
227
|
# We only need this in the case we don't already
|
196
228
|
# have a numeric validator, because otherwise
|
197
229
|
# it will cause duplicate validation errors
|
198
|
-
|
230
|
+
validation_options = options_with_message(options, property, :is_primitive)
|
231
|
+
property.model.validates_primitive_type_of property.name, validation_options
|
199
232
|
end
|
200
233
|
end
|
201
234
|
|
202
|
-
|
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
|
203
241
|
|
204
|
-
|
205
|
-
|
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
|
206
249
|
end
|
207
250
|
end # module AutoValidations
|
208
251
|
end # module Validations
|
@@ -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
|
@@ -2,8 +2,6 @@ require 'forwardable'
|
|
2
2
|
|
3
3
|
module DataMapper
|
4
4
|
module Validations
|
5
|
-
|
6
|
-
##
|
7
5
|
#
|
8
6
|
# @author Guy van den Berg
|
9
7
|
# @since 0.9
|
@@ -15,12 +13,15 @@ module DataMapper
|
|
15
13
|
#
|
16
14
|
|
17
15
|
def_delegators :@contexts, :empty?, :each
|
16
|
+
def_delegators :@attributes, :[]
|
18
17
|
include Enumerable
|
19
18
|
|
20
|
-
attr_reader :contexts
|
19
|
+
attr_reader :contexts, :attributes
|
21
20
|
|
22
|
-
def initialize
|
23
|
-
@
|
21
|
+
def initialize(model = nil)
|
22
|
+
@model = model
|
23
|
+
@contexts = {}
|
24
|
+
@attributes = {}
|
24
25
|
end
|
25
26
|
|
26
27
|
#
|
@@ -30,78 +31,188 @@ module DataMapper
|
|
30
31
|
# Return an array of validators for a named context
|
31
32
|
#
|
32
33
|
# @param [String]
|
33
|
-
# Context name for which return validators
|
34
|
+
# Context name for which to return validators
|
34
35
|
# @return [Array<DataMapper::Validations::GenericValidator>]
|
35
|
-
# An array of validators
|
36
|
+
# An array of validators bound to the given context
|
36
37
|
def context(name)
|
37
|
-
contexts[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
|
38
49
|
end
|
39
50
|
|
40
51
|
# Clear all named context validators off of the resource
|
41
52
|
#
|
42
53
|
def clear!
|
43
54
|
contexts.clear
|
55
|
+
attributes.clear
|
44
56
|
end
|
45
57
|
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
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
|
49
150
|
#
|
50
|
-
# @
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
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
|
54
186
|
# @return [Boolean]
|
55
187
|
# true if all are valid, otherwise false
|
56
188
|
def execute(named_context, target)
|
57
189
|
target.errors.clear!
|
58
190
|
|
59
|
-
|
60
|
-
|
191
|
+
available_validators = context(named_context)
|
192
|
+
executable_validators = available_validators.select { |v| v.execute?(target) }
|
61
193
|
|
62
|
-
# By default we start the list with the full set of runnable validators.
|
63
|
-
#
|
64
194
|
# In the case of a new Resource or regular ruby class instance,
|
65
195
|
# everything needs to be validated completely, and no eager-loading
|
66
196
|
# logic should apply.
|
67
197
|
#
|
68
|
-
# In the case of a DM::Resource that isn't new, we optimize:
|
69
|
-
#
|
70
|
-
# 1. Eager-load all lazy, not-yet-loaded properties that need
|
71
|
-
# validation, all at once.
|
72
|
-
#
|
73
|
-
# 2. Limit run validators to
|
74
|
-
# - those applied to dirty attributes only,
|
75
|
-
# - those that should always run (presence/absence)
|
76
|
-
# - those that don't reference any real properties (field-less
|
77
|
-
# block validators, validations in virtual attributes)
|
78
198
|
if target.kind_of?(DataMapper::Resource) && !target.new?
|
79
|
-
|
80
|
-
dirty_attrs = target.dirty_attributes.keys.map{ |p| p.name }
|
81
|
-
validators = runnable_validators.select{|v|
|
82
|
-
!attrs.include?(v.field_name) || dirty_attrs.include?(v.field_name)
|
83
|
-
}
|
84
|
-
|
85
|
-
# Load all lazy, not-yet-loaded properties that need validation,
|
86
|
-
# all at once.
|
87
|
-
fields_to_load = validators.map{|v|
|
88
|
-
target.class.properties[v.field_name]
|
89
|
-
}.compact.select {|p|
|
90
|
-
p.lazy? && !p.loaded?(target)
|
91
|
-
}
|
92
|
-
|
93
|
-
target.__send__(:eager_load, fields_to_load)
|
94
|
-
|
95
|
-
# Finally include any validators that should always run or don't
|
96
|
-
# reference any real properties (field-less block vaildators).
|
97
|
-
validators |= runnable_validators.select do |v|
|
98
|
-
[ MethodValidator, PresenceValidator, AbsenceValidator ].any? do |klass|
|
99
|
-
v.kind_of?(klass)
|
100
|
-
end
|
101
|
-
end
|
199
|
+
load_validated_properties(target, executable_validators)
|
102
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
|
+
}
|
103
214
|
|
104
|
-
|
215
|
+
resource.__send__(:eager_load, properties_to_load)
|
105
216
|
end
|
106
217
|
|
107
218
|
end # module ContextualValidators
|