activemodel 5.1.7 → 5.2.0.beta1

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 (64) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +32 -93
  3. data/README.rdoc +1 -1
  4. data/lib/active_model.rb +6 -1
  5. data/lib/active_model/attribute.rb +243 -0
  6. data/lib/active_model/attribute/user_provided_default.rb +30 -0
  7. data/lib/active_model/attribute_assignment.rb +8 -5
  8. data/lib/active_model/attribute_methods.rb +12 -10
  9. data/lib/active_model/attribute_mutation_tracker.rb +116 -0
  10. data/lib/active_model/attribute_set.rb +113 -0
  11. data/lib/active_model/attribute_set/builder.rb +124 -0
  12. data/lib/active_model/attribute_set/yaml_encoder.rb +41 -0
  13. data/lib/active_model/attributes.rb +108 -0
  14. data/lib/active_model/callbacks.rb +7 -2
  15. data/lib/active_model/conversion.rb +2 -0
  16. data/lib/active_model/dirty.rb +124 -57
  17. data/lib/active_model/errors.rb +32 -21
  18. data/lib/active_model/forbidden_attributes_protection.rb +2 -0
  19. data/lib/active_model/gem_version.rb +5 -3
  20. data/lib/active_model/lint.rb +2 -0
  21. data/lib/active_model/model.rb +2 -0
  22. data/lib/active_model/naming.rb +5 -3
  23. data/lib/active_model/railtie.rb +2 -0
  24. data/lib/active_model/secure_password.rb +5 -3
  25. data/lib/active_model/serialization.rb +2 -0
  26. data/lib/active_model/serializers/json.rb +3 -2
  27. data/lib/active_model/translation.rb +2 -0
  28. data/lib/active_model/type.rb +6 -0
  29. data/lib/active_model/type/big_integer.rb +2 -0
  30. data/lib/active_model/type/binary.rb +2 -0
  31. data/lib/active_model/type/boolean.rb +2 -0
  32. data/lib/active_model/type/date.rb +2 -0
  33. data/lib/active_model/type/date_time.rb +6 -0
  34. data/lib/active_model/type/decimal.rb +2 -0
  35. data/lib/active_model/type/float.rb +2 -0
  36. data/lib/active_model/type/helpers.rb +2 -0
  37. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +6 -0
  38. data/lib/active_model/type/helpers/mutable.rb +2 -0
  39. data/lib/active_model/type/helpers/numeric.rb +2 -0
  40. data/lib/active_model/type/helpers/time_value.rb +2 -1
  41. data/lib/active_model/type/immutable_string.rb +2 -0
  42. data/lib/active_model/type/integer.rb +3 -1
  43. data/lib/active_model/type/registry.rb +2 -0
  44. data/lib/active_model/type/string.rb +2 -0
  45. data/lib/active_model/type/time.rb +8 -4
  46. data/lib/active_model/type/value.rb +3 -1
  47. data/lib/active_model/validations.rb +7 -3
  48. data/lib/active_model/validations/absence.rb +2 -0
  49. data/lib/active_model/validations/acceptance.rb +2 -0
  50. data/lib/active_model/validations/callbacks.rb +11 -13
  51. data/lib/active_model/validations/clusivity.rb +2 -0
  52. data/lib/active_model/validations/confirmation.rb +3 -1
  53. data/lib/active_model/validations/exclusion.rb +2 -0
  54. data/lib/active_model/validations/format.rb +1 -0
  55. data/lib/active_model/validations/helper_methods.rb +2 -0
  56. data/lib/active_model/validations/inclusion.rb +2 -0
  57. data/lib/active_model/validations/length.rb +10 -2
  58. data/lib/active_model/validations/numericality.rb +3 -1
  59. data/lib/active_model/validations/presence.rb +1 -0
  60. data/lib/active_model/validations/validates.rb +4 -3
  61. data/lib/active_model/validations/with.rb +2 -0
  62. data/lib/active_model/validator.rb +6 -4
  63. data/lib/active_model/version.rb +2 -0
  64. metadata +17 -9
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/deep_dup"
4
+ require "active_model/attribute_set"
5
+ require "active_model/attribute/user_provided_default"
6
+
7
+ module ActiveModel
8
+ module Attributes #:nodoc:
9
+ extend ActiveSupport::Concern
10
+ include ActiveModel::AttributeMethods
11
+
12
+ included do
13
+ attribute_method_suffix "="
14
+ class_attribute :attribute_types, :_default_attributes, instance_accessor: false
15
+ self.attribute_types = Hash.new(Type.default_value)
16
+ self._default_attributes = AttributeSet.new({})
17
+ end
18
+
19
+ module ClassMethods
20
+ def attribute(name, type = Type::Value.new, **options)
21
+ name = name.to_s
22
+ if type.is_a?(Symbol)
23
+ type = ActiveModel::Type.lookup(type, **options.except(:default))
24
+ end
25
+ self.attribute_types = attribute_types.merge(name => type)
26
+ define_default_attribute(name, options.fetch(:default, NO_DEFAULT_PROVIDED), type)
27
+ define_attribute_methods(name)
28
+ end
29
+
30
+ private
31
+
32
+ def define_method_attribute=(name)
33
+ safe_name = name.unpack("h*".freeze).first
34
+ ActiveModel::AttributeMethods::AttrNames.set_name_cache safe_name, name
35
+
36
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
37
+ def __temp__#{safe_name}=(value)
38
+ name = ::ActiveModel::AttributeMethods::AttrNames::ATTR_#{safe_name}
39
+ write_attribute(name, value)
40
+ end
41
+ alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
42
+ undef_method :__temp__#{safe_name}=
43
+ STR
44
+ end
45
+
46
+ NO_DEFAULT_PROVIDED = Object.new # :nodoc:
47
+ private_constant :NO_DEFAULT_PROVIDED
48
+
49
+ def define_default_attribute(name, value, type)
50
+ self._default_attributes = _default_attributes.deep_dup
51
+ if value == NO_DEFAULT_PROVIDED
52
+ default_attribute = _default_attributes[name].with_type(type)
53
+ else
54
+ default_attribute = Attribute::UserProvidedDefault.new(
55
+ name,
56
+ value,
57
+ type,
58
+ _default_attributes.fetch(name.to_s) { nil },
59
+ )
60
+ end
61
+ _default_attributes[name] = default_attribute
62
+ end
63
+ end
64
+
65
+ def initialize(*)
66
+ @attributes = self.class._default_attributes.deep_dup
67
+ super
68
+ end
69
+
70
+ private
71
+
72
+ def write_attribute(attr_name, value)
73
+ name = if self.class.attribute_alias?(attr_name)
74
+ self.class.attribute_alias(attr_name).to_s
75
+ else
76
+ attr_name.to_s
77
+ end
78
+
79
+ @attributes.write_from_user(name, value)
80
+ value
81
+ end
82
+
83
+ def attribute(attr_name)
84
+ name = if self.class.attribute_alias?(attr_name)
85
+ self.class.attribute_alias(attr_name).to_s
86
+ else
87
+ attr_name.to_s
88
+ end
89
+ @attributes.fetch_value(name)
90
+ end
91
+
92
+ # Handle *= for method_missing.
93
+ def attribute=(attribute_name, value)
94
+ write_attribute(attribute_name, value)
95
+ end
96
+ end
97
+
98
+ module AttributeMethods #:nodoc:
99
+ AttrNames = Module.new {
100
+ def self.set_name_cache(name, value)
101
+ const_name = "ATTR_#{name}"
102
+ unless const_defined? const_name
103
+ const_set const_name, value.dup.freeze
104
+ end
105
+ end
106
+ }
107
+ end
108
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/core_ext/array/extract_options"
2
4
 
3
5
  module ActiveModel
@@ -56,6 +58,9 @@ module ActiveModel
56
58
  #
57
59
  # Would only create the +after_create+ and +before_create+ callback methods in
58
60
  # your class.
61
+ #
62
+ # NOTE: Calling the same callback multiple times will overwrite previous callback definitions.
63
+ #
59
64
  module Callbacks
60
65
  def self.extended(base) #:nodoc:
61
66
  base.class_eval do
@@ -98,8 +103,8 @@ module ActiveModel
98
103
  # end
99
104
  # end
100
105
  #
101
- # NOTE: +method_name+ passed to `define_model_callbacks` must not end with
102
- # `!`, `?` or `=`.
106
+ # NOTE: +method_name+ passed to define_model_callbacks must not end with
107
+ # <tt>!</tt>, <tt>?</tt> or <tt>=</tt>.
103
108
  def define_model_callbacks(*callbacks)
104
109
  options = callbacks.extract_options!
105
110
  options = {
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModel
2
4
  # == Active \Model \Conversion
3
5
  #
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/hash_with_indifferent_access"
2
4
  require "active_support/core_ext/object/duplicable"
5
+ require "active_model/attribute_mutation_tracker"
3
6
 
4
7
  module ActiveModel
5
8
  # == Active \Model \Dirty
@@ -128,6 +131,24 @@ module ActiveModel
128
131
  attribute_method_affix prefix: "restore_", suffix: "!"
129
132
  end
130
133
 
134
+ def initialize_dup(other) # :nodoc:
135
+ super
136
+ if self.class.respond_to?(:_default_attributes)
137
+ @attributes = self.class._default_attributes.map do |attr|
138
+ attr.with_value_from_user(@attributes.fetch_value(attr.name))
139
+ end
140
+ end
141
+ @mutations_from_database = nil
142
+ end
143
+
144
+ def changes_applied # :nodoc:
145
+ @previously_changed = changes
146
+ @mutations_before_last_save = mutations_from_database
147
+ @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
148
+ forget_attribute_assignments
149
+ @mutations_from_database = nil
150
+ end
151
+
131
152
  # Returns +true+ if any of the attributes have unsaved changes, +false+ otherwise.
132
153
  #
133
154
  # person.changed? # => false
@@ -146,6 +167,60 @@ module ActiveModel
146
167
  changed_attributes.keys
147
168
  end
148
169
 
170
+ # Handles <tt>*_changed?</tt> for +method_missing+.
171
+ def attribute_changed?(attr, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) # :nodoc:
172
+ !!changes_include?(attr) &&
173
+ (to == OPTION_NOT_GIVEN || to == _read_attribute(attr)) &&
174
+ (from == OPTION_NOT_GIVEN || from == changed_attributes[attr])
175
+ end
176
+
177
+ # Handles <tt>*_was</tt> for +method_missing+.
178
+ def attribute_was(attr) # :nodoc:
179
+ attribute_changed?(attr) ? changed_attributes[attr] : _read_attribute(attr)
180
+ end
181
+
182
+ # Handles <tt>*_previously_changed?</tt> for +method_missing+.
183
+ def attribute_previously_changed?(attr) #:nodoc:
184
+ previous_changes_include?(attr)
185
+ end
186
+
187
+ # Restore all previous data of the provided attributes.
188
+ def restore_attributes(attributes = changed)
189
+ attributes.each { |attr| restore_attribute! attr }
190
+ end
191
+
192
+ # Clears all dirty data: current changes and previous changes.
193
+ def clear_changes_information
194
+ @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
195
+ @mutations_before_last_save = nil
196
+ @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
197
+ forget_attribute_assignments
198
+ @mutations_from_database = nil
199
+ end
200
+
201
+ def clear_attribute_changes(attr_names)
202
+ attributes_changed_by_setter.except!(*attr_names)
203
+ attr_names.each do |attr_name|
204
+ clear_attribute_change(attr_name)
205
+ end
206
+ end
207
+
208
+ # Returns a hash of the attributes with unsaved changes indicating their original
209
+ # values like <tt>attr => original value</tt>.
210
+ #
211
+ # person.name # => "bob"
212
+ # person.name = 'robert'
213
+ # person.changed_attributes # => {"name" => "bob"}
214
+ def changed_attributes
215
+ # This should only be set by methods which will call changed_attributes
216
+ # multiple times when it is known that the computed value cannot change.
217
+ if defined?(@cached_changed_attributes)
218
+ @cached_changed_attributes
219
+ else
220
+ attributes_changed_by_setter.reverse_merge(mutations_from_database.changed_values).freeze
221
+ end
222
+ end
223
+
149
224
  # Returns a hash of changed attributes indicating their original
150
225
  # and new values like <tt>attr => [original value, new value]</tt>.
151
226
  #
@@ -153,7 +228,9 @@ module ActiveModel
153
228
  # person.name = 'bob'
154
229
  # person.changes # => { "name" => ["bill", "bob"] }
155
230
  def changes
156
- ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
231
+ cache_changed_attributes do
232
+ ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
233
+ end
157
234
  end
158
235
 
159
236
  # Returns a hash of attributes that were changed before the model was saved.
@@ -164,45 +241,51 @@ module ActiveModel
164
241
  # person.previous_changes # => {"name" => ["bob", "robert"]}
165
242
  def previous_changes
166
243
  @previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new
244
+ @previously_changed.merge(mutations_before_last_save.changes)
167
245
  end
168
246
 
169
- # Returns a hash of the attributes with unsaved changes indicating their original
170
- # values like <tt>attr => original value</tt>.
171
- #
172
- # person.name # => "bob"
173
- # person.name = 'robert'
174
- # person.changed_attributes # => {"name" => "bob"}
175
- def changed_attributes
176
- @changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
247
+ def attribute_changed_in_place?(attr_name) # :nodoc:
248
+ mutations_from_database.changed_in_place?(attr_name)
177
249
  end
178
250
 
179
- # Handles <tt>*_changed?</tt> for +method_missing+.
180
- def attribute_changed?(attr, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) # :nodoc:
181
- !!changes_include?(attr) &&
182
- (to == OPTION_NOT_GIVEN || to == __send__(attr)) &&
183
- (from == OPTION_NOT_GIVEN || from == changed_attributes[attr])
184
- end
251
+ private
252
+ def clear_attribute_change(attr_name)
253
+ mutations_from_database.forget_change(attr_name)
254
+ end
185
255
 
186
- # Handles <tt>*_was</tt> for +method_missing+.
187
- def attribute_was(attr) # :nodoc:
188
- attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
189
- end
256
+ def mutations_from_database
257
+ unless defined?(@mutations_from_database)
258
+ @mutations_from_database = nil
259
+ end
260
+ @mutations_from_database ||= if defined?(@attributes)
261
+ ActiveModel::AttributeMutationTracker.new(@attributes)
262
+ else
263
+ NullMutationTracker.instance
264
+ end
265
+ end
190
266
 
191
- # Handles <tt>*_previously_changed?</tt> for +method_missing+.
192
- def attribute_previously_changed?(attr) #:nodoc:
193
- previous_changes_include?(attr)
194
- end
267
+ def forget_attribute_assignments
268
+ @attributes = @attributes.map(&:forgetting_assignment) if defined?(@attributes)
269
+ end
195
270
 
196
- # Restore all previous data of the provided attributes.
197
- def restore_attributes(attributes = changed)
198
- attributes.each { |attr| restore_attribute! attr }
199
- end
271
+ def mutations_before_last_save
272
+ @mutations_before_last_save ||= ActiveModel::NullMutationTracker.instance
273
+ end
200
274
 
201
- private
275
+ def cache_changed_attributes
276
+ @cached_changed_attributes = changed_attributes
277
+ yield
278
+ ensure
279
+ clear_changed_attributes_cache
280
+ end
281
+
282
+ def clear_changed_attributes_cache
283
+ remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
284
+ end
202
285
 
203
286
  # Returns +true+ if attr_name is changed, +false+ otherwise.
204
287
  def changes_include?(attr_name)
205
- attributes_changed_by_setter.include?(attr_name)
288
+ attributes_changed_by_setter.include?(attr_name) || mutations_from_database.changed?(attr_name)
206
289
  end
207
290
  alias attribute_changed_by_setter? changes_include?
208
291
 
@@ -212,21 +295,9 @@ module ActiveModel
212
295
  previous_changes.include?(attr_name)
213
296
  end
214
297
 
215
- # Removes current changes and makes them accessible through +previous_changes+.
216
- def changes_applied # :doc:
217
- @previously_changed = changes
218
- @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
219
- end
220
-
221
- # Clears all dirty data: current changes and previous changes.
222
- def clear_changes_information # :doc:
223
- @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
224
- @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
225
- end
226
-
227
298
  # Handles <tt>*_change</tt> for +method_missing+.
228
299
  def attribute_change(attr)
229
- [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
300
+ [changed_attributes[attr], _read_attribute(attr)] if attribute_changed?(attr)
230
301
  end
231
302
 
232
303
  # Handles <tt>*_previous_change</tt> for +method_missing+.
@@ -236,15 +307,16 @@ module ActiveModel
236
307
 
237
308
  # Handles <tt>*_will_change!</tt> for +method_missing+.
238
309
  def attribute_will_change!(attr)
239
- return if attribute_changed?(attr)
310
+ unless attribute_changed?(attr)
311
+ begin
312
+ value = _read_attribute(attr)
313
+ value = value.duplicable? ? value.clone : value
314
+ rescue TypeError, NoMethodError
315
+ end
240
316
 
241
- begin
242
- value = __send__(attr)
243
- value = value.duplicable? ? value.clone : value
244
- rescue TypeError, NoMethodError
317
+ set_attribute_was(attr, value)
245
318
  end
246
-
247
- set_attribute_was(attr, value)
319
+ mutations_from_database.force_change(attr)
248
320
  end
249
321
 
250
322
  # Handles <tt>restore_*!</tt> for +method_missing+.
@@ -255,18 +327,13 @@ module ActiveModel
255
327
  end
256
328
  end
257
329
 
258
- # This is necessary because `changed_attributes` might be overridden in
259
- # other implementations (e.g. in `ActiveRecord`)
260
- alias_method :attributes_changed_by_setter, :changed_attributes # :nodoc:
330
+ def attributes_changed_by_setter
331
+ @attributes_changed_by_setter ||= ActiveSupport::HashWithIndifferentAccess.new
332
+ end
261
333
 
262
334
  # Force an attribute to have a particular "before" value
263
335
  def set_attribute_was(attr, old_value)
264
336
  attributes_changed_by_setter[attr] = old_value
265
337
  end
266
-
267
- # Remove changes information for the provided attributes.
268
- def clear_attribute_changes(attributes) # :doc:
269
- attributes_changed_by_setter.except!(*attributes)
270
- end
271
338
  end
272
339
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/core_ext/array/conversions"
2
4
  require "active_support/core_ext/string/inflections"
3
5
  require "active_support/core_ext/object/deep_dup"
@@ -93,6 +95,18 @@ module ActiveModel
93
95
  @details = other.details.dup
94
96
  end
95
97
 
98
+ # Merges the errors from <tt>other</tt>.
99
+ #
100
+ # other - The ActiveModel::Errors instance.
101
+ #
102
+ # Examples
103
+ #
104
+ # person.errors.merge!(other)
105
+ def merge!(other)
106
+ @messages.merge!(other.messages) { |_, ary1, ary2| ary1 + ary2 }
107
+ @details.merge!(other.details) { |_, ary1, ary2| ary1 + ary2 }
108
+ end
109
+
96
110
  # Clear the error messages.
97
111
  #
98
112
  # person.errors.full_messages # => ["name cannot be nil"]
@@ -132,15 +146,6 @@ module ActiveModel
132
146
  #
133
147
  # person.errors[:name] # => ["cannot be nil"]
134
148
  # person.errors['name'] # => ["cannot be nil"]
135
- #
136
- # Note that, if you try to get errors of an attribute which has
137
- # no errors associated with it, this method will instantiate
138
- # an empty error list for it and +keys+ will return an array
139
- # of error keys which includes this attribute.
140
- #
141
- # person.errors.keys # => []
142
- # person.errors[:name] # => []
143
- # person.errors.keys # => [:name]
144
149
  def [](attribute)
145
150
  messages[attribute.to_sym]
146
151
  end
@@ -181,7 +186,9 @@ module ActiveModel
181
186
  # person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
182
187
  # person.errors.values # => [["cannot be nil", "must be specified"]]
183
188
  def values
184
- messages.values
189
+ messages.select do |key, value|
190
+ !value.empty?
191
+ end.values
185
192
  end
186
193
 
187
194
  # Returns all message keys.
@@ -189,7 +196,9 @@ module ActiveModel
189
196
  # person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
190
197
  # person.errors.keys # => [:name]
191
198
  def keys
192
- messages.keys
199
+ messages.select do |key, value|
200
+ !value.empty?
201
+ end.keys
193
202
  end
194
203
 
195
204
  # Returns +true+ if no errors are found, +false+ otherwise.
@@ -313,9 +322,13 @@ module ActiveModel
313
322
  # person.errors.added? :name, :too_long # => false
314
323
  # person.errors.added? :name, "is too long" # => false
315
324
  def added?(attribute, message = :invalid, options = {})
316
- message = message.call if message.respond_to?(:call)
317
- message = normalize_message(attribute, message, options)
318
- self[attribute].include? message
325
+ if message.is_a? Symbol
326
+ self.details[attribute].map { |e| e[:error] }.include? message
327
+ else
328
+ message = message.call if message.respond_to?(:call)
329
+ message = normalize_message(attribute, message, options)
330
+ self[attribute].include? message
331
+ end
319
332
  end
320
333
 
321
334
  # Returns all the full error messages in an array.
@@ -389,21 +402,19 @@ module ActiveModel
389
402
  type = options.delete(:message) if options[:message].is_a?(Symbol)
390
403
 
391
404
  if @base.class.respond_to?(:i18n_scope)
392
- defaults = @base.class.lookup_ancestors.map do |klass|
393
- [ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
394
- :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
405
+ i18n_scope = @base.class.i18n_scope.to_s
406
+ defaults = @base.class.lookup_ancestors.flat_map do |klass|
407
+ [ :"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
408
+ :"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
395
409
  end
410
+ defaults << :"#{i18n_scope}.errors.messages.#{type}"
396
411
  else
397
412
  defaults = []
398
413
  end
399
414
 
400
- defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}" if @base.class.respond_to?(:i18n_scope)
401
415
  defaults << :"errors.attributes.#{attribute}.#{type}"
402
416
  defaults << :"errors.messages.#{type}"
403
417
 
404
- defaults.compact!
405
- defaults.flatten!
406
-
407
418
  key = defaults.shift
408
419
  defaults = options.delete(:message) if options[:message]
409
420
  value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil)