activemodel 5.2.8.1 → 6.1.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +91 -97
  3. data/MIT-LICENSE +1 -2
  4. data/README.rdoc +6 -4
  5. data/lib/active_model/attribute/user_provided_default.rb +1 -2
  6. data/lib/active_model/attribute.rb +21 -21
  7. data/lib/active_model/attribute_assignment.rb +4 -6
  8. data/lib/active_model/attribute_methods.rb +117 -40
  9. data/lib/active_model/attribute_mutation_tracker.rb +90 -33
  10. data/lib/active_model/attribute_set/builder.rb +81 -16
  11. data/lib/active_model/attribute_set/yaml_encoder.rb +1 -2
  12. data/lib/active_model/attribute_set.rb +20 -28
  13. data/lib/active_model/attributes.rb +65 -44
  14. data/lib/active_model/callbacks.rb +11 -9
  15. data/lib/active_model/conversion.rb +1 -1
  16. data/lib/active_model/dirty.rb +51 -101
  17. data/lib/active_model/error.rb +207 -0
  18. data/lib/active_model/errors.rb +347 -155
  19. data/lib/active_model/gem_version.rb +3 -3
  20. data/lib/active_model/lint.rb +1 -1
  21. data/lib/active_model/naming.rb +22 -7
  22. data/lib/active_model/nested_error.rb +22 -0
  23. data/lib/active_model/railtie.rb +6 -0
  24. data/lib/active_model/secure_password.rb +55 -55
  25. data/lib/active_model/serialization.rb +9 -7
  26. data/lib/active_model/serializers/json.rb +17 -9
  27. data/lib/active_model/translation.rb +1 -1
  28. data/lib/active_model/type/big_integer.rb +0 -1
  29. data/lib/active_model/type/binary.rb +1 -1
  30. data/lib/active_model/type/boolean.rb +0 -1
  31. data/lib/active_model/type/date.rb +0 -5
  32. data/lib/active_model/type/date_time.rb +3 -8
  33. data/lib/active_model/type/decimal.rb +0 -1
  34. data/lib/active_model/type/float.rb +2 -3
  35. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +14 -6
  36. data/lib/active_model/type/helpers/numeric.rb +17 -6
  37. data/lib/active_model/type/helpers/time_value.rb +37 -15
  38. data/lib/active_model/type/helpers/timezone.rb +1 -1
  39. data/lib/active_model/type/immutable_string.rb +14 -11
  40. data/lib/active_model/type/integer.rb +15 -18
  41. data/lib/active_model/type/registry.rb +17 -19
  42. data/lib/active_model/type/string.rb +12 -3
  43. data/lib/active_model/type/time.rb +1 -6
  44. data/lib/active_model/type/value.rb +9 -2
  45. data/lib/active_model/type.rb +3 -2
  46. data/lib/active_model/validations/absence.rb +2 -2
  47. data/lib/active_model/validations/acceptance.rb +34 -27
  48. data/lib/active_model/validations/callbacks.rb +15 -16
  49. data/lib/active_model/validations/clusivity.rb +6 -3
  50. data/lib/active_model/validations/confirmation.rb +4 -4
  51. data/lib/active_model/validations/exclusion.rb +1 -1
  52. data/lib/active_model/validations/format.rb +2 -3
  53. data/lib/active_model/validations/inclusion.rb +2 -2
  54. data/lib/active_model/validations/length.rb +3 -3
  55. data/lib/active_model/validations/numericality.rb +58 -44
  56. data/lib/active_model/validations/presence.rb +1 -1
  57. data/lib/active_model/validations/validates.rb +7 -6
  58. data/lib/active_model/validations.rb +6 -9
  59. data/lib/active_model/validator.rb +8 -3
  60. data/lib/active_model.rb +2 -1
  61. metadata +13 -7
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/enumerable"
3
4
  require "active_support/core_ext/object/deep_dup"
4
5
  require "active_model/attribute_set/builder"
5
6
  require "active_model/attribute_set/yaml_encoder"
@@ -13,11 +14,11 @@ module ActiveModel
13
14
  end
14
15
 
15
16
  def [](name)
16
- attributes[name] || Attribute.null(name)
17
+ @attributes[name] || default_attribute(name)
17
18
  end
18
19
 
19
20
  def []=(name, value)
20
- attributes[name] = value
21
+ @attributes[name] = value
21
22
  end
22
23
 
23
24
  def values_before_type_cast
@@ -25,9 +26,9 @@ module ActiveModel
25
26
  end
26
27
 
27
28
  def to_hash
28
- initialized_attributes.transform_values(&:value)
29
+ keys.index_with { |name| self[name].value }
29
30
  end
30
- alias_method :to_h, :to_hash
31
+ alias :to_h :to_hash
31
32
 
32
33
  def key?(name)
33
34
  attributes.key?(name) && self[name].initialized?
@@ -37,48 +38,41 @@ module ActiveModel
37
38
  attributes.each_key.select { |name| self[name].initialized? }
38
39
  end
39
40
 
40
- if defined?(JRUBY_VERSION)
41
- # This form is significantly faster on JRuby, and this is one of our biggest hotspots.
42
- # https://github.com/jruby/jruby/pull/2562
43
- def fetch_value(name, &block)
44
- self[name].value(&block)
45
- end
46
- else
47
- def fetch_value(name)
48
- self[name].value { |n| yield n if block_given? }
49
- end
41
+ def fetch_value(name, &block)
42
+ self[name].value(&block)
50
43
  end
51
44
 
52
45
  def write_from_database(name, value)
53
- attributes[name] = self[name].with_value_from_database(value)
46
+ @attributes[name] = self[name].with_value_from_database(value)
54
47
  end
55
48
 
56
49
  def write_from_user(name, value)
57
- attributes[name] = self[name].with_value_from_user(value)
50
+ raise FrozenError, "can't modify frozen attributes" if frozen?
51
+ @attributes[name] = self[name].with_value_from_user(value)
52
+ value
58
53
  end
59
54
 
60
55
  def write_cast_value(name, value)
61
- attributes[name] = self[name].with_cast_value(value)
56
+ @attributes[name] = self[name].with_cast_value(value)
57
+ value
62
58
  end
63
59
 
64
60
  def freeze
65
- @attributes.freeze
61
+ attributes.freeze
66
62
  super
67
63
  end
68
64
 
69
65
  def deep_dup
70
- self.class.allocate.tap do |copy|
71
- copy.instance_variable_set(:@attributes, attributes.deep_dup)
72
- end
66
+ AttributeSet.new(attributes.deep_dup)
73
67
  end
74
68
 
75
69
  def initialize_dup(_)
76
- @attributes = attributes.dup
70
+ @attributes = @attributes.dup
77
71
  super
78
72
  end
79
73
 
80
74
  def initialize_clone(_)
81
- @attributes = attributes.clone
75
+ @attributes = @attributes.clone
82
76
  super
83
77
  end
84
78
 
@@ -89,7 +83,7 @@ module ActiveModel
89
83
  end
90
84
 
91
85
  def accessed
92
- attributes.select { |_, attr| attr.has_been_read? }.keys
86
+ attributes.each_key.select { |name| self[name].has_been_read? }
93
87
  end
94
88
 
95
89
  def map(&block)
@@ -102,13 +96,11 @@ module ActiveModel
102
96
  end
103
97
 
104
98
  protected
105
-
106
99
  attr_reader :attributes
107
100
 
108
101
  private
109
-
110
- def initialized_attributes
111
- attributes.select { |_, attr| attr.initialized? }
102
+ def default_attribute(name)
103
+ Attribute.null(name)
112
104
  end
113
105
  end
114
106
  end
@@ -26,20 +26,31 @@ module ActiveModel
26
26
  define_attribute_method(name)
27
27
  end
28
28
 
29
- private
30
-
31
- def define_method_attribute=(name)
32
- safe_name = name.unpack("h*".freeze).first
33
- ActiveModel::AttributeMethods::AttrNames.set_name_cache safe_name, name
29
+ # Returns an array of attribute names as strings
30
+ #
31
+ # class Person
32
+ # include ActiveModel::Attributes
33
+ #
34
+ # attribute :name, :string
35
+ # attribute :age, :integer
36
+ # end
37
+ #
38
+ # Person.attribute_names
39
+ # # => ["name", "age"]
40
+ def attribute_names
41
+ attribute_types.keys
42
+ end
34
43
 
35
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
36
- def __temp__#{safe_name}=(value)
37
- name = ::ActiveModel::AttributeMethods::AttrNames::ATTR_#{safe_name}
38
- write_attribute(name, value)
39
- end
40
- alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
41
- undef_method :__temp__#{safe_name}=
42
- STR
44
+ private
45
+ def define_method_attribute=(name, owner:)
46
+ ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
47
+ owner, name, writer: true,
48
+ ) do |temp_method_name, attr_name_expr|
49
+ owner <<
50
+ "def #{temp_method_name}(value)" <<
51
+ " _write_attribute(#{attr_name_expr}, value)" <<
52
+ "end"
53
+ end
43
54
  end
44
55
 
45
56
  NO_DEFAULT_PROVIDED = Object.new # :nodoc:
@@ -66,46 +77,56 @@ module ActiveModel
66
77
  super
67
78
  end
68
79
 
80
+ def initialize_dup(other) # :nodoc:
81
+ @attributes = @attributes.deep_dup
82
+ super
83
+ end
84
+
85
+ # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
86
+ #
87
+ # class Person
88
+ # include ActiveModel::Attributes
89
+ #
90
+ # attribute :name, :string
91
+ # attribute :age, :integer
92
+ # end
93
+ #
94
+ # person = Person.new(name: 'Francesco', age: 22)
95
+ # person.attributes
96
+ # # => {"name"=>"Francesco", "age"=>22}
69
97
  def attributes
70
98
  @attributes.to_hash
71
99
  end
72
100
 
73
- private
101
+ # Returns an array of attribute names as strings
102
+ #
103
+ # class Person
104
+ # include ActiveModel::Attributes
105
+ #
106
+ # attribute :name, :string
107
+ # attribute :age, :integer
108
+ # end
109
+ #
110
+ # person = Person.new
111
+ # person.attribute_names
112
+ # # => ["name", "age"]
113
+ def attribute_names
114
+ @attributes.keys
115
+ end
74
116
 
75
- def write_attribute(attr_name, value)
76
- name = if self.class.attribute_alias?(attr_name)
77
- self.class.attribute_alias(attr_name).to_s
78
- else
79
- attr_name.to_s
80
- end
117
+ def freeze
118
+ @attributes = @attributes.clone.freeze unless frozen?
119
+ super
120
+ end
81
121
 
82
- @attributes.write_from_user(name, value)
83
- value
122
+ private
123
+ def _write_attribute(attr_name, value)
124
+ @attributes.write_from_user(attr_name, value)
84
125
  end
126
+ alias :attribute= :_write_attribute
85
127
 
86
128
  def attribute(attr_name)
87
- name = if self.class.attribute_alias?(attr_name)
88
- self.class.attribute_alias(attr_name).to_s
89
- else
90
- attr_name.to_s
91
- end
92
- @attributes.fetch_value(name)
93
- end
94
-
95
- # Handle *= for method_missing.
96
- def attribute=(attribute_name, value)
97
- write_attribute(attribute_name, value)
98
- end
99
- end
100
-
101
- module AttributeMethods #:nodoc:
102
- AttrNames = Module.new {
103
- def self.set_name_cache(name, value)
104
- const_name = "ATTR_#{name}"
105
- unless const_defined? const_name
106
- const_set const_name, value.dup.freeze
107
- end
129
+ @attributes.fetch_value(attr_name)
108
130
  end
109
- }
110
131
  end
111
132
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/array/extract_options"
4
+ require "active_support/core_ext/hash/keys"
4
5
 
5
6
  module ActiveModel
6
7
  # == Active \Model \Callbacks
@@ -125,28 +126,29 @@ module ActiveModel
125
126
  end
126
127
 
127
128
  private
128
-
129
129
  def _define_before_model_callback(klass, callback)
130
- klass.define_singleton_method("before_#{callback}") do |*args, &block|
131
- set_callback(:"#{callback}", :before, *args, &block)
130
+ klass.define_singleton_method("before_#{callback}") do |*args, **options, &block|
131
+ options.assert_valid_keys(:if, :unless, :prepend)
132
+ set_callback(:"#{callback}", :before, *args, options, &block)
132
133
  end
133
134
  end
134
135
 
135
136
  def _define_around_model_callback(klass, callback)
136
- klass.define_singleton_method("around_#{callback}") do |*args, &block|
137
- set_callback(:"#{callback}", :around, *args, &block)
137
+ klass.define_singleton_method("around_#{callback}") do |*args, **options, &block|
138
+ options.assert_valid_keys(:if, :unless, :prepend)
139
+ set_callback(:"#{callback}", :around, *args, options, &block)
138
140
  end
139
141
  end
140
142
 
141
143
  def _define_after_model_callback(klass, callback)
142
- klass.define_singleton_method("after_#{callback}") do |*args, &block|
143
- options = args.extract_options!
144
+ klass.define_singleton_method("after_#{callback}") do |*args, **options, &block|
145
+ options.assert_valid_keys(:if, :unless, :prepend)
144
146
  options[:prepend] = true
145
147
  conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v|
146
148
  v != false
147
149
  }
148
- options[:if] = Array(options[:if]) << conditional
149
- set_callback(:"#{callback}", :after, *(args << options), &block)
150
+ options[:if] = Array(options[:if]) + [conditional]
151
+ set_callback(:"#{callback}", :after, *args, options, &block)
150
152
  end
151
153
  end
152
154
  end
@@ -103,7 +103,7 @@ module ActiveModel
103
103
  @_to_partial_path ||= begin
104
104
  element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name))
105
105
  collection = ActiveSupport::Inflector.tableize(name)
106
- "#{collection}/#{element}".freeze
106
+ "#{collection}/#{element}"
107
107
  end
108
108
  end
109
109
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/hash_with_indifferent_access"
4
- require "active_support/core_ext/object/duplicable"
5
3
  require "active_model/attribute_mutation_tracker"
6
4
 
7
5
  module ActiveModel
@@ -85,7 +83,9 @@ module ActiveModel
85
83
  #
86
84
  # person.previous_changes # => {"name" => [nil, "Bill"]}
87
85
  # person.name_previously_changed? # => true
86
+ # person.name_previously_changed?(from: nil, to: "Bill") # => true
88
87
  # person.name_previous_change # => [nil, "Bill"]
88
+ # person.name_previously_was # => nil
89
89
  # person.reload!
90
90
  # person.previous_changes # => {}
91
91
  #
@@ -122,13 +122,11 @@ module ActiveModel
122
122
  extend ActiveSupport::Concern
123
123
  include ActiveModel::AttributeMethods
124
124
 
125
- OPTION_NOT_GIVEN = Object.new # :nodoc:
126
- private_constant :OPTION_NOT_GIVEN
127
-
128
125
  included do
129
126
  attribute_method_suffix "_changed?", "_change", "_will_change!", "_was"
130
- attribute_method_suffix "_previously_changed?", "_previous_change"
127
+ attribute_method_suffix "_previously_changed?", "_previous_change", "_previously_was"
131
128
  attribute_method_affix prefix: "restore_", suffix: "!"
129
+ attribute_method_affix prefix: "clear_", suffix: "_change"
132
130
  end
133
131
 
134
132
  def initialize_dup(other) # :nodoc:
@@ -141,25 +139,29 @@ module ActiveModel
141
139
  @mutations_from_database = nil
142
140
  end
143
141
 
144
- # Clears dirty data and moves +changes+ to +previously_changed+ and
142
+ def as_json(options = {}) # :nodoc:
143
+ options[:except] = [*options[:except], "mutations_from_database", "mutations_before_last_save"]
144
+ super(options)
145
+ end
146
+
147
+ # Clears dirty data and moves +changes+ to +previous_changes+ and
145
148
  # +mutations_from_database+ to +mutations_before_last_save+ respectively.
146
149
  def changes_applied
147
150
  unless defined?(@attributes)
148
- @previously_changed = changes
151
+ mutations_from_database.finalize_changes
149
152
  end
150
153
  @mutations_before_last_save = mutations_from_database
151
- @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
152
154
  forget_attribute_assignments
153
155
  @mutations_from_database = nil
154
156
  end
155
157
 
156
- # Returns +true+ if any of the attributes have unsaved changes, +false+ otherwise.
158
+ # Returns +true+ if any of the attributes has unsaved changes, +false+ otherwise.
157
159
  #
158
160
  # person.changed? # => false
159
161
  # person.name = 'bob'
160
162
  # person.changed? # => true
161
163
  def changed?
162
- changed_attributes.present?
164
+ mutations_from_database.any_changes?
163
165
  end
164
166
 
165
167
  # Returns an array with the name of the attributes with unsaved changes.
@@ -168,42 +170,42 @@ module ActiveModel
168
170
  # person.name = 'bob'
169
171
  # person.changed # => ["name"]
170
172
  def changed
171
- changed_attributes.keys
173
+ mutations_from_database.changed_attribute_names
172
174
  end
173
175
 
174
- # Handles <tt>*_changed?</tt> for +method_missing+.
175
- def attribute_changed?(attr, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) # :nodoc:
176
- !!changes_include?(attr) &&
177
- (to == OPTION_NOT_GIVEN || to == _read_attribute(attr)) &&
178
- (from == OPTION_NOT_GIVEN || from == changed_attributes[attr])
176
+ # Dispatch target for <tt>*_changed?</tt> attribute methods.
177
+ def attribute_changed?(attr_name, **options) # :nodoc:
178
+ mutations_from_database.changed?(attr_name.to_s, **options)
179
179
  end
180
180
 
181
- # Handles <tt>*_was</tt> for +method_missing+.
182
- def attribute_was(attr) # :nodoc:
183
- attribute_changed?(attr) ? changed_attributes[attr] : _read_attribute(attr)
181
+ # Dispatch target for <tt>*_was</tt> attribute methods.
182
+ def attribute_was(attr_name) # :nodoc:
183
+ mutations_from_database.original_value(attr_name.to_s)
184
184
  end
185
185
 
186
- # Handles <tt>*_previously_changed?</tt> for +method_missing+.
187
- def attribute_previously_changed?(attr) #:nodoc:
188
- previous_changes_include?(attr)
186
+ # Dispatch target for <tt>*_previously_changed?</tt> attribute methods.
187
+ def attribute_previously_changed?(attr_name, **options) # :nodoc:
188
+ mutations_before_last_save.changed?(attr_name.to_s, **options)
189
+ end
190
+
191
+ # Dispatch target for <tt>*_previously_was</tt> attribute methods.
192
+ def attribute_previously_was(attr_name) # :nodoc:
193
+ mutations_before_last_save.original_value(attr_name.to_s)
189
194
  end
190
195
 
191
196
  # Restore all previous data of the provided attributes.
192
- def restore_attributes(attributes = changed)
193
- attributes.each { |attr| restore_attribute! attr }
197
+ def restore_attributes(attr_names = changed)
198
+ attr_names.each { |attr_name| restore_attribute!(attr_name) }
194
199
  end
195
200
 
196
201
  # Clears all dirty data: current changes and previous changes.
197
202
  def clear_changes_information
198
- @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
199
203
  @mutations_before_last_save = nil
200
- @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
201
204
  forget_attribute_assignments
202
205
  @mutations_from_database = nil
203
206
  end
204
207
 
205
208
  def clear_attribute_changes(attr_names)
206
- attributes_changed_by_setter.except!(*attr_names)
207
209
  attr_names.each do |attr_name|
208
210
  clear_attribute_change(attr_name)
209
211
  end
@@ -216,13 +218,7 @@ module ActiveModel
216
218
  # person.name = 'robert'
217
219
  # person.changed_attributes # => {"name" => "bob"}
218
220
  def changed_attributes
219
- # This should only be set by methods which will call changed_attributes
220
- # multiple times when it is known that the computed value cannot change.
221
- if defined?(@cached_changed_attributes)
222
- @cached_changed_attributes
223
- else
224
- attributes_changed_by_setter.reverse_merge(mutations_from_database.changed_values).freeze
225
- end
221
+ mutations_from_database.changed_values
226
222
  end
227
223
 
228
224
  # Returns a hash of changed attributes indicating their original
@@ -232,9 +228,7 @@ module ActiveModel
232
228
  # person.name = 'bob'
233
229
  # person.changes # => { "name" => ["bill", "bob"] }
234
230
  def changes
235
- cache_changed_attributes do
236
- ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
237
- end
231
+ mutations_from_database.changes
238
232
  end
239
233
 
240
234
  # Returns a hash of attributes that were changed before the model was saved.
@@ -244,27 +238,23 @@ module ActiveModel
244
238
  # person.save
245
239
  # person.previous_changes # => {"name" => ["bob", "robert"]}
246
240
  def previous_changes
247
- @previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new
248
- @previously_changed.merge(mutations_before_last_save.changes)
241
+ mutations_before_last_save.changes
249
242
  end
250
243
 
251
244
  def attribute_changed_in_place?(attr_name) # :nodoc:
252
- mutations_from_database.changed_in_place?(attr_name)
245
+ mutations_from_database.changed_in_place?(attr_name.to_s)
253
246
  end
254
247
 
255
248
  private
256
249
  def clear_attribute_change(attr_name)
257
- mutations_from_database.forget_change(attr_name)
250
+ mutations_from_database.forget_change(attr_name.to_s)
258
251
  end
259
252
 
260
253
  def mutations_from_database
261
- unless defined?(@mutations_from_database)
262
- @mutations_from_database = nil
263
- end
264
254
  @mutations_from_database ||= if defined?(@attributes)
265
255
  ActiveModel::AttributeMutationTracker.new(@attributes)
266
256
  else
267
- NullMutationTracker.instance
257
+ ActiveModel::ForcedMutationTracker.new(self)
268
258
  end
269
259
  end
270
260
 
@@ -276,68 +266,28 @@ module ActiveModel
276
266
  @mutations_before_last_save ||= ActiveModel::NullMutationTracker.instance
277
267
  end
278
268
 
279
- def cache_changed_attributes
280
- @cached_changed_attributes = changed_attributes
281
- yield
282
- ensure
283
- clear_changed_attributes_cache
284
- end
285
-
286
- def clear_changed_attributes_cache
287
- remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
288
- end
289
-
290
- # Returns +true+ if attr_name is changed, +false+ otherwise.
291
- def changes_include?(attr_name)
292
- attributes_changed_by_setter.include?(attr_name) || mutations_from_database.changed?(attr_name)
293
- end
294
- alias attribute_changed_by_setter? changes_include?
295
-
296
- # Returns +true+ if attr_name were changed before the model was saved,
297
- # +false+ otherwise.
298
- def previous_changes_include?(attr_name)
299
- previous_changes.include?(attr_name)
269
+ # Dispatch target for <tt>*_change</tt> attribute methods.
270
+ def attribute_change(attr_name)
271
+ mutations_from_database.change_to_attribute(attr_name.to_s)
300
272
  end
301
273
 
302
- # Handles <tt>*_change</tt> for +method_missing+.
303
- def attribute_change(attr)
304
- [changed_attributes[attr], _read_attribute(attr)] if attribute_changed?(attr)
274
+ # Dispatch target for <tt>*_previous_change</tt> attribute methods.
275
+ def attribute_previous_change(attr_name)
276
+ mutations_before_last_save.change_to_attribute(attr_name.to_s)
305
277
  end
306
278
 
307
- # Handles <tt>*_previous_change</tt> for +method_missing+.
308
- def attribute_previous_change(attr)
309
- previous_changes[attr] if attribute_previously_changed?(attr)
279
+ # Dispatch target for <tt>*_will_change!</tt> attribute methods.
280
+ def attribute_will_change!(attr_name)
281
+ mutations_from_database.force_change(attr_name.to_s)
310
282
  end
311
283
 
312
- # Handles <tt>*_will_change!</tt> for +method_missing+.
313
- def attribute_will_change!(attr)
314
- unless attribute_changed?(attr)
315
- begin
316
- value = _read_attribute(attr)
317
- value = value.duplicable? ? value.clone : value
318
- rescue TypeError, NoMethodError
319
- end
320
-
321
- set_attribute_was(attr, value)
284
+ # Dispatch target for <tt>restore_*!</tt> attribute methods.
285
+ def restore_attribute!(attr_name)
286
+ attr_name = attr_name.to_s
287
+ if attribute_changed?(attr_name)
288
+ __send__("#{attr_name}=", attribute_was(attr_name))
289
+ clear_attribute_change(attr_name)
322
290
  end
323
- mutations_from_database.force_change(attr)
324
- end
325
-
326
- # Handles <tt>restore_*!</tt> for +method_missing+.
327
- def restore_attribute!(attr)
328
- if attribute_changed?(attr)
329
- __send__("#{attr}=", changed_attributes[attr])
330
- clear_attribute_changes([attr])
331
- end
332
- end
333
-
334
- def attributes_changed_by_setter
335
- @attributes_changed_by_setter ||= ActiveSupport::HashWithIndifferentAccess.new
336
- end
337
-
338
- # Force an attribute to have a particular "before" value
339
- def set_attribute_was(attr, old_value)
340
- attributes_changed_by_setter[attr] = old_value
341
291
  end
342
292
  end
343
293
  end