dm-validations 1.1.0 → 1.2.0.rc1
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.
- 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
|