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.
Files changed (44) hide show
  1. data/Gemfile +8 -8
  2. data/Rakefile +2 -2
  3. data/VERSION +1 -1
  4. data/dm-validations.gemspec +29 -139
  5. data/lib/dm-validations.rb +51 -103
  6. data/lib/dm-validations/auto_validate.rb +104 -61
  7. data/lib/dm-validations/context.rb +66 -0
  8. data/lib/dm-validations/contextual_validators.rb +164 -53
  9. data/lib/dm-validations/formats/email.rb +41 -23
  10. data/lib/dm-validations/formats/url.rb +6 -1
  11. data/lib/dm-validations/support/object.rb +13 -0
  12. data/lib/dm-validations/validation_errors.rb +27 -19
  13. data/lib/dm-validations/validators/absent_field_validator.rb +11 -11
  14. data/lib/dm-validations/validators/acceptance_validator.rb +15 -13
  15. data/lib/dm-validations/validators/block_validator.rb +12 -11
  16. data/lib/dm-validations/validators/confirmation_validator.rb +23 -16
  17. data/lib/dm-validations/validators/format_validator.rb +45 -23
  18. data/lib/dm-validations/validators/generic_validator.rb +85 -38
  19. data/lib/dm-validations/validators/length_validator.rb +61 -26
  20. data/lib/dm-validations/validators/method_validator.rb +13 -17
  21. data/lib/dm-validations/validators/numeric_validator.rb +35 -35
  22. data/lib/dm-validations/validators/primitive_validator.rb +11 -12
  23. data/lib/dm-validations/validators/required_field_validator.rb +11 -13
  24. data/lib/dm-validations/validators/uniqueness_validator.rb +15 -14
  25. data/lib/dm-validations/validators/within_validator.rb +30 -12
  26. data/spec/fixtures/bill_of_landing.rb +5 -0
  27. data/spec/fixtures/llama_spaceship.rb +15 -0
  28. data/spec/integration/automatic_validation/custom_messages_for_inferred_validation_spec.rb +10 -0
  29. data/spec/integration/automatic_validation/disabling_inferred_validation_spec.rb +8 -0
  30. data/spec/integration/automatic_validation/inferred_length_validation_spec.rb +8 -0
  31. data/spec/integration/automatic_validation/inferred_uniqueness_validation_spec.rb +6 -2
  32. data/spec/integration/automatic_validation/inferred_within_validation_spec.rb +6 -0
  33. data/spec/integration/datamapper_models/association_validation_spec.rb +5 -2
  34. data/spec/integration/dirty_attributes/dirty_attributes_spec.rb +13 -0
  35. data/spec/integration/format_validator/email_format_validator_spec.rb +13 -5
  36. data/spec/integration/format_validator/url_format_validator_spec.rb +28 -2
  37. data/spec/integration/required_field_validator/association_spec.rb +5 -1
  38. data/spec/public/resource_spec.rb +2 -2
  39. data/spec/spec_helper.rb +1 -1
  40. data/spec/unit/contextual_validators/emptiness_spec.rb +10 -10
  41. data/spec/unit/contextual_validators/execution_spec.rb +4 -4
  42. data/spec/unit/validators/within_validator_spec.rb +23 -0
  43. metadata +81 -146
  44. data/lib/dm-validations/support/context.rb +0 -93
@@ -1,38 +1,53 @@
1
1
  module DataMapper
2
- class Property
3
- # for options_with_message
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
- # adds message for validator
12
- def options_with_message(base_options, property, validator_name)
13
- options = base_options.clone
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]
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
- options
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
- def without_auto_validations(&block)
30
- @disable_auto_validations = true
31
- block.call
32
- @disable_auto_validations = false
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 if
50
- # the value is a Range the validation will set :within => value
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
- def auto_generate_validations(property)
85
- return if disabled_auto_validations? || skip_auto_validation_for?(property)
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 # auto_generate_validations
119
+ end # generate_for_property
102
120
 
103
- # Checks whether auto validations are currently
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 positive value
122
- def skip_auto_validation_for?(property)
123
- property.options.key?(:auto_validation) && !property.options[:auto_validation]
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
- def infer_presence_validation_for(property, options)
137
+ # @api private
138
+ def self.infer_presence_validation_for(property, options)
127
139
  return if skip_presence_validation?(property)
128
140
 
129
- validates_presence_of property.name, options_with_message(options, property, :presence)
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
- def infer_length_validation_for(property, options)
133
- return unless [ DataMapper::Property::String, DataMapper::Property::Text ].any? { |klass| property.kind_of?(klass) }
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
- validates_length_of property.name, options_with_message(options, property, :length)
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
- def infer_format_validation_for(property, options)
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
- validates_format_of property.name, options_with_message(options, property, :format)
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
- def infer_uniqueness_validation_for(property, options)
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
- validates_uniqueness_of property.name, options_with_message(options, property, :is_unique)
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
- validates_uniqueness_of property.name, options_with_message(options, property, :is_unique)
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
- def infer_within_validation_for(property, options)
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
- validates_within property.name, options_with_message(options, property, :within)
201
+ validation_options = options_with_message(options, property, :within)
202
+ property.model.validates_within property.name, validation_options
175
203
  end
176
204
 
177
- def infer_type_validation_for(property, options)
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
- validates_numericality_of property.name, options_with_message(options, property, :is_number)
189
- elsif BigDecimal == property.primitive || Float == property.primitive
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
- validates_numericality_of property.name, options_with_message(options, property, :is_number)
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
- validates_primitive_type_of property.name, options_with_message(options, property, :is_primitive)
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
- private
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
- def skip_presence_validation?(property)
205
- property.allow_blank? || property.serial?
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
- @contexts = {}
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
- # Execute all validators in the named context against the target. Load
47
- # together any properties that are designated lazy but are not yet loaded.
48
- # Optionally only validate dirty properties.
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
- # @param [Symbol]
51
- # named_context the context we are validating against
52
- # @param [Object]
53
- # target the resource that we are validating
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
- runnable_validators = context(named_context).select{ |validator| validator.execute?(target) }
60
- validators = runnable_validators.dup
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
- attrs = target.attributes.keys
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
- validators.map { |validator| validator.call(target) }.all?
215
+ resource.__send__(:eager_load, properties_to_load)
105
216
  end
106
217
 
107
218
  end # module ContextualValidators