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.
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