activemodel 5.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +114 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +264 -0
  5. data/lib/active_model.rb +77 -0
  6. data/lib/active_model/attribute.rb +248 -0
  7. data/lib/active_model/attribute/user_provided_default.rb +52 -0
  8. data/lib/active_model/attribute_assignment.rb +57 -0
  9. data/lib/active_model/attribute_methods.rb +478 -0
  10. data/lib/active_model/attribute_mutation_tracker.rb +124 -0
  11. data/lib/active_model/attribute_set.rb +114 -0
  12. data/lib/active_model/attribute_set/builder.rb +126 -0
  13. data/lib/active_model/attribute_set/yaml_encoder.rb +41 -0
  14. data/lib/active_model/attributes.rb +111 -0
  15. data/lib/active_model/callbacks.rb +153 -0
  16. data/lib/active_model/conversion.rb +111 -0
  17. data/lib/active_model/dirty.rb +343 -0
  18. data/lib/active_model/errors.rb +517 -0
  19. data/lib/active_model/forbidden_attributes_protection.rb +31 -0
  20. data/lib/active_model/gem_version.rb +17 -0
  21. data/lib/active_model/lint.rb +118 -0
  22. data/lib/active_model/locale/en.yml +36 -0
  23. data/lib/active_model/model.rb +99 -0
  24. data/lib/active_model/naming.rb +318 -0
  25. data/lib/active_model/railtie.rb +14 -0
  26. data/lib/active_model/secure_password.rb +129 -0
  27. data/lib/active_model/serialization.rb +192 -0
  28. data/lib/active_model/serializers/json.rb +146 -0
  29. data/lib/active_model/translation.rb +70 -0
  30. data/lib/active_model/type.rb +53 -0
  31. data/lib/active_model/type/big_integer.rb +15 -0
  32. data/lib/active_model/type/binary.rb +52 -0
  33. data/lib/active_model/type/boolean.rb +38 -0
  34. data/lib/active_model/type/date.rb +57 -0
  35. data/lib/active_model/type/date_time.rb +51 -0
  36. data/lib/active_model/type/decimal.rb +70 -0
  37. data/lib/active_model/type/float.rb +36 -0
  38. data/lib/active_model/type/helpers.rb +7 -0
  39. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +41 -0
  40. data/lib/active_model/type/helpers/mutable.rb +20 -0
  41. data/lib/active_model/type/helpers/numeric.rb +37 -0
  42. data/lib/active_model/type/helpers/time_value.rb +68 -0
  43. data/lib/active_model/type/helpers/timezone.rb +19 -0
  44. data/lib/active_model/type/immutable_string.rb +32 -0
  45. data/lib/active_model/type/integer.rb +70 -0
  46. data/lib/active_model/type/registry.rb +70 -0
  47. data/lib/active_model/type/string.rb +26 -0
  48. data/lib/active_model/type/time.rb +51 -0
  49. data/lib/active_model/type/value.rb +126 -0
  50. data/lib/active_model/validations.rb +439 -0
  51. data/lib/active_model/validations/absence.rb +33 -0
  52. data/lib/active_model/validations/acceptance.rb +106 -0
  53. data/lib/active_model/validations/callbacks.rb +122 -0
  54. data/lib/active_model/validations/clusivity.rb +54 -0
  55. data/lib/active_model/validations/confirmation.rb +80 -0
  56. data/lib/active_model/validations/exclusion.rb +49 -0
  57. data/lib/active_model/validations/format.rb +114 -0
  58. data/lib/active_model/validations/helper_methods.rb +15 -0
  59. data/lib/active_model/validations/inclusion.rb +47 -0
  60. data/lib/active_model/validations/length.rb +129 -0
  61. data/lib/active_model/validations/numericality.rb +189 -0
  62. data/lib/active_model/validations/presence.rb +39 -0
  63. data/lib/active_model/validations/validates.rb +174 -0
  64. data/lib/active_model/validations/with.rb +147 -0
  65. data/lib/active_model/validator.rb +183 -0
  66. data/lib/active_model/version.rb +10 -0
  67. metadata +125 -0
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/array/extract_options"
4
+
5
+ module ActiveModel
6
+ # == Active \Model \Callbacks
7
+ #
8
+ # Provides an interface for any class to have Active Record like callbacks.
9
+ #
10
+ # Like the Active Record methods, the callback chain is aborted as soon as
11
+ # one of the methods throws +:abort+.
12
+ #
13
+ # First, extend ActiveModel::Callbacks from the class you are creating:
14
+ #
15
+ # class MyModel
16
+ # extend ActiveModel::Callbacks
17
+ # end
18
+ #
19
+ # Then define a list of methods that you want callbacks attached to:
20
+ #
21
+ # define_model_callbacks :create, :update
22
+ #
23
+ # This will provide all three standard callbacks (before, around and after)
24
+ # for both the <tt>:create</tt> and <tt>:update</tt> methods. To implement,
25
+ # you need to wrap the methods you want callbacks on in a block so that the
26
+ # callbacks get a chance to fire:
27
+ #
28
+ # def create
29
+ # run_callbacks :create do
30
+ # # Your create action methods here
31
+ # end
32
+ # end
33
+ #
34
+ # Then in your class, you can use the +before_create+, +after_create+ and
35
+ # +around_create+ methods, just as you would in an Active Record model.
36
+ #
37
+ # before_create :action_before_create
38
+ #
39
+ # def action_before_create
40
+ # # Your code here
41
+ # end
42
+ #
43
+ # When defining an around callback remember to yield to the block, otherwise
44
+ # it won't be executed:
45
+ #
46
+ # around_create :log_status
47
+ #
48
+ # def log_status
49
+ # puts 'going to call the block...'
50
+ # yield
51
+ # puts 'block successfully called.'
52
+ # end
53
+ #
54
+ # You can choose to have only specific callbacks by passing a hash to the
55
+ # +define_model_callbacks+ method.
56
+ #
57
+ # define_model_callbacks :create, only: [:after, :before]
58
+ #
59
+ # Would only create the +after_create+ and +before_create+ callback methods in
60
+ # your class.
61
+ #
62
+ # NOTE: Calling the same callback multiple times will overwrite previous callback definitions.
63
+ #
64
+ module Callbacks
65
+ def self.extended(base) #:nodoc:
66
+ base.class_eval do
67
+ include ActiveSupport::Callbacks
68
+ end
69
+ end
70
+
71
+ # define_model_callbacks accepts the same options +define_callbacks+ does,
72
+ # in case you want to overwrite a default. Besides that, it also accepts an
73
+ # <tt>:only</tt> option, where you can choose if you want all types (before,
74
+ # around or after) or just some.
75
+ #
76
+ # define_model_callbacks :initializer, only: :after
77
+ #
78
+ # Note, the <tt>only: <type></tt> hash will apply to all callbacks defined
79
+ # on that method call. To get around this you can call the define_model_callbacks
80
+ # method as many times as you need.
81
+ #
82
+ # define_model_callbacks :create, only: :after
83
+ # define_model_callbacks :update, only: :before
84
+ # define_model_callbacks :destroy, only: :around
85
+ #
86
+ # Would create +after_create+, +before_update+ and +around_destroy+ methods
87
+ # only.
88
+ #
89
+ # You can pass in a class to before_<type>, after_<type> and around_<type>,
90
+ # in which case the callback will call that class's <action>_<type> method
91
+ # passing the object that the callback is being called on.
92
+ #
93
+ # class MyModel
94
+ # extend ActiveModel::Callbacks
95
+ # define_model_callbacks :create
96
+ #
97
+ # before_create AnotherClass
98
+ # end
99
+ #
100
+ # class AnotherClass
101
+ # def self.before_create( obj )
102
+ # # obj is the MyModel instance that the callback is being called on
103
+ # end
104
+ # end
105
+ #
106
+ # NOTE: +method_name+ passed to define_model_callbacks must not end with
107
+ # <tt>!</tt>, <tt>?</tt> or <tt>=</tt>.
108
+ def define_model_callbacks(*callbacks)
109
+ options = callbacks.extract_options!
110
+ options = {
111
+ skip_after_callbacks_if_terminated: true,
112
+ scope: [:kind, :name],
113
+ only: [:before, :around, :after]
114
+ }.merge!(options)
115
+
116
+ types = Array(options.delete(:only))
117
+
118
+ callbacks.each do |callback|
119
+ define_callbacks(callback, options)
120
+
121
+ types.each do |type|
122
+ send("_define_#{type}_model_callback", self, callback)
123
+ end
124
+ end
125
+ end
126
+
127
+ private
128
+
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)
132
+ end
133
+ end
134
+
135
+ def _define_around_model_callback(klass, callback)
136
+ klass.define_singleton_method("around_#{callback}") do |*args, &block|
137
+ set_callback(:"#{callback}", :around, *args, &block)
138
+ end
139
+ end
140
+
141
+ def _define_after_model_callback(klass, callback)
142
+ klass.define_singleton_method("after_#{callback}") do |*args, &block|
143
+ options = args.extract_options!
144
+ options[:prepend] = true
145
+ conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v|
146
+ v != false
147
+ }
148
+ options[:if] = Array(options[:if]) << conditional
149
+ set_callback(:"#{callback}", :after, *(args << options), &block)
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ # == Active \Model \Conversion
5
+ #
6
+ # Handles default conversions: to_model, to_key, to_param, and to_partial_path.
7
+ #
8
+ # Let's take for example this non-persisted object.
9
+ #
10
+ # class ContactMessage
11
+ # include ActiveModel::Conversion
12
+ #
13
+ # # ContactMessage are never persisted in the DB
14
+ # def persisted?
15
+ # false
16
+ # end
17
+ # end
18
+ #
19
+ # cm = ContactMessage.new
20
+ # cm.to_model == cm # => true
21
+ # cm.to_key # => nil
22
+ # cm.to_param # => nil
23
+ # cm.to_partial_path # => "contact_messages/contact_message"
24
+ module Conversion
25
+ extend ActiveSupport::Concern
26
+
27
+ # If your object is already designed to implement all of the \Active \Model
28
+ # you can use the default <tt>:to_model</tt> implementation, which simply
29
+ # returns +self+.
30
+ #
31
+ # class Person
32
+ # include ActiveModel::Conversion
33
+ # end
34
+ #
35
+ # person = Person.new
36
+ # person.to_model == person # => true
37
+ #
38
+ # If your model does not act like an \Active \Model object, then you should
39
+ # define <tt>:to_model</tt> yourself returning a proxy object that wraps
40
+ # your object with \Active \Model compliant methods.
41
+ def to_model
42
+ self
43
+ end
44
+
45
+ # Returns an Array of all key attributes if any of the attributes is set, whether or not
46
+ # the object is persisted. Returns +nil+ if there are no key attributes.
47
+ #
48
+ # class Person
49
+ # include ActiveModel::Conversion
50
+ # attr_accessor :id
51
+ #
52
+ # def initialize(id)
53
+ # @id = id
54
+ # end
55
+ # end
56
+ #
57
+ # person = Person.new(1)
58
+ # person.to_key # => [1]
59
+ def to_key
60
+ key = respond_to?(:id) && id
61
+ key ? [key] : nil
62
+ end
63
+
64
+ # Returns a +string+ representing the object's key suitable for use in URLs,
65
+ # or +nil+ if <tt>persisted?</tt> is +false+.
66
+ #
67
+ # class Person
68
+ # include ActiveModel::Conversion
69
+ # attr_accessor :id
70
+ #
71
+ # def initialize(id)
72
+ # @id = id
73
+ # end
74
+ #
75
+ # def persisted?
76
+ # true
77
+ # end
78
+ # end
79
+ #
80
+ # person = Person.new(1)
81
+ # person.to_param # => "1"
82
+ def to_param
83
+ (persisted? && key = to_key) ? key.join("-") : nil
84
+ end
85
+
86
+ # Returns a +string+ identifying the path associated with the object.
87
+ # ActionPack uses this to find a suitable partial to represent the object.
88
+ #
89
+ # class Person
90
+ # include ActiveModel::Conversion
91
+ # end
92
+ #
93
+ # person = Person.new
94
+ # person.to_partial_path # => "people/person"
95
+ def to_partial_path
96
+ self.class._to_partial_path
97
+ end
98
+
99
+ module ClassMethods #:nodoc:
100
+ # Provide a class level cache for #to_partial_path. This is an
101
+ # internal method and should not be accessed directly.
102
+ def _to_partial_path #:nodoc:
103
+ @_to_partial_path ||= begin
104
+ element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name))
105
+ collection = ActiveSupport::Inflector.tableize(name)
106
+ "#{collection}/#{element}".freeze
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,343 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/hash_with_indifferent_access"
4
+ require "active_support/core_ext/object/duplicable"
5
+ require "active_model/attribute_mutation_tracker"
6
+
7
+ module ActiveModel
8
+ # == Active \Model \Dirty
9
+ #
10
+ # Provides a way to track changes in your object in the same way as
11
+ # Active Record does.
12
+ #
13
+ # The requirements for implementing ActiveModel::Dirty are:
14
+ #
15
+ # * <tt>include ActiveModel::Dirty</tt> in your object.
16
+ # * Call <tt>define_attribute_methods</tt> passing each method you want to
17
+ # track.
18
+ # * Call <tt>[attr_name]_will_change!</tt> before each change to the tracked
19
+ # attribute.
20
+ # * Call <tt>changes_applied</tt> after the changes are persisted.
21
+ # * Call <tt>clear_changes_information</tt> when you want to reset the changes
22
+ # information.
23
+ # * Call <tt>restore_attributes</tt> when you want to restore previous data.
24
+ #
25
+ # A minimal implementation could be:
26
+ #
27
+ # class Person
28
+ # include ActiveModel::Dirty
29
+ #
30
+ # define_attribute_methods :name
31
+ #
32
+ # def initialize
33
+ # @name = nil
34
+ # end
35
+ #
36
+ # def name
37
+ # @name
38
+ # end
39
+ #
40
+ # def name=(val)
41
+ # name_will_change! unless val == @name
42
+ # @name = val
43
+ # end
44
+ #
45
+ # def save
46
+ # # do persistence work
47
+ #
48
+ # changes_applied
49
+ # end
50
+ #
51
+ # def reload!
52
+ # # get the values from the persistence layer
53
+ #
54
+ # clear_changes_information
55
+ # end
56
+ #
57
+ # def rollback!
58
+ # restore_attributes
59
+ # end
60
+ # end
61
+ #
62
+ # A newly instantiated +Person+ object is unchanged:
63
+ #
64
+ # person = Person.new
65
+ # person.changed? # => false
66
+ #
67
+ # Change the name:
68
+ #
69
+ # person.name = 'Bob'
70
+ # person.changed? # => true
71
+ # person.name_changed? # => true
72
+ # person.name_changed?(from: nil, to: "Bob") # => true
73
+ # person.name_was # => nil
74
+ # person.name_change # => [nil, "Bob"]
75
+ # person.name = 'Bill'
76
+ # person.name_change # => [nil, "Bill"]
77
+ #
78
+ # Save the changes:
79
+ #
80
+ # person.save
81
+ # person.changed? # => false
82
+ # person.name_changed? # => false
83
+ #
84
+ # Reset the changes:
85
+ #
86
+ # person.previous_changes # => {"name" => [nil, "Bill"]}
87
+ # person.name_previously_changed? # => true
88
+ # person.name_previous_change # => [nil, "Bill"]
89
+ # person.reload!
90
+ # person.previous_changes # => {}
91
+ #
92
+ # Rollback the changes:
93
+ #
94
+ # person.name = "Uncle Bob"
95
+ # person.rollback!
96
+ # person.name # => "Bill"
97
+ # person.name_changed? # => false
98
+ #
99
+ # Assigning the same value leaves the attribute unchanged:
100
+ #
101
+ # person.name = 'Bill'
102
+ # person.name_changed? # => false
103
+ # person.name_change # => nil
104
+ #
105
+ # Which attributes have changed?
106
+ #
107
+ # person.name = 'Bob'
108
+ # person.changed # => ["name"]
109
+ # person.changes # => {"name" => ["Bill", "Bob"]}
110
+ #
111
+ # If an attribute is modified in-place then make use of
112
+ # <tt>[attribute_name]_will_change!</tt> to mark that the attribute is changing.
113
+ # Otherwise \Active \Model can't track changes to in-place attributes. Note
114
+ # that Active Record can detect in-place modifications automatically. You do
115
+ # not need to call <tt>[attribute_name]_will_change!</tt> on Active Record models.
116
+ #
117
+ # person.name_will_change!
118
+ # person.name_change # => ["Bill", "Bill"]
119
+ # person.name << 'y'
120
+ # person.name_change # => ["Bill", "Billy"]
121
+ module Dirty
122
+ extend ActiveSupport::Concern
123
+ include ActiveModel::AttributeMethods
124
+
125
+ OPTION_NOT_GIVEN = Object.new # :nodoc:
126
+ private_constant :OPTION_NOT_GIVEN
127
+
128
+ included do
129
+ attribute_method_suffix "_changed?", "_change", "_will_change!", "_was"
130
+ attribute_method_suffix "_previously_changed?", "_previous_change"
131
+ attribute_method_affix prefix: "restore_", suffix: "!"
132
+ end
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
+ # Clears dirty data and moves +changes+ to +previously_changed+ and
145
+ # +mutations_from_database+ to +mutations_before_last_save+ respectively.
146
+ def changes_applied
147
+ unless defined?(@attributes)
148
+ @previously_changed = changes
149
+ end
150
+ @mutations_before_last_save = mutations_from_database
151
+ @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
152
+ forget_attribute_assignments
153
+ @mutations_from_database = nil
154
+ end
155
+
156
+ # Returns +true+ if any of the attributes have unsaved changes, +false+ otherwise.
157
+ #
158
+ # person.changed? # => false
159
+ # person.name = 'bob'
160
+ # person.changed? # => true
161
+ def changed?
162
+ changed_attributes.present?
163
+ end
164
+
165
+ # Returns an array with the name of the attributes with unsaved changes.
166
+ #
167
+ # person.changed # => []
168
+ # person.name = 'bob'
169
+ # person.changed # => ["name"]
170
+ def changed
171
+ changed_attributes.keys
172
+ end
173
+
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])
179
+ end
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)
184
+ end
185
+
186
+ # Handles <tt>*_previously_changed?</tt> for +method_missing+.
187
+ def attribute_previously_changed?(attr) #:nodoc:
188
+ previous_changes_include?(attr)
189
+ end
190
+
191
+ # Restore all previous data of the provided attributes.
192
+ def restore_attributes(attributes = changed)
193
+ attributes.each { |attr| restore_attribute! attr }
194
+ end
195
+
196
+ # Clears all dirty data: current changes and previous changes.
197
+ def clear_changes_information
198
+ @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
199
+ @mutations_before_last_save = nil
200
+ @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
201
+ forget_attribute_assignments
202
+ @mutations_from_database = nil
203
+ end
204
+
205
+ def clear_attribute_changes(attr_names)
206
+ attributes_changed_by_setter.except!(*attr_names)
207
+ attr_names.each do |attr_name|
208
+ clear_attribute_change(attr_name)
209
+ end
210
+ end
211
+
212
+ # Returns a hash of the attributes with unsaved changes indicating their original
213
+ # values like <tt>attr => original value</tt>.
214
+ #
215
+ # person.name # => "bob"
216
+ # person.name = 'robert'
217
+ # person.changed_attributes # => {"name" => "bob"}
218
+ 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
226
+ end
227
+
228
+ # Returns a hash of changed attributes indicating their original
229
+ # and new values like <tt>attr => [original value, new value]</tt>.
230
+ #
231
+ # person.changes # => {}
232
+ # person.name = 'bob'
233
+ # person.changes # => { "name" => ["bill", "bob"] }
234
+ def changes
235
+ cache_changed_attributes do
236
+ ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
237
+ end
238
+ end
239
+
240
+ # Returns a hash of attributes that were changed before the model was saved.
241
+ #
242
+ # person.name # => "bob"
243
+ # person.name = 'robert'
244
+ # person.save
245
+ # person.previous_changes # => {"name" => ["bob", "robert"]}
246
+ def previous_changes
247
+ @previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new
248
+ @previously_changed.merge(mutations_before_last_save.changes)
249
+ end
250
+
251
+ def attribute_changed_in_place?(attr_name) # :nodoc:
252
+ mutations_from_database.changed_in_place?(attr_name)
253
+ end
254
+
255
+ private
256
+ def clear_attribute_change(attr_name)
257
+ mutations_from_database.forget_change(attr_name)
258
+ end
259
+
260
+ def mutations_from_database
261
+ unless defined?(@mutations_from_database)
262
+ @mutations_from_database = nil
263
+ end
264
+ @mutations_from_database ||= if defined?(@attributes)
265
+ ActiveModel::AttributeMutationTracker.new(@attributes)
266
+ else
267
+ NullMutationTracker.instance
268
+ end
269
+ end
270
+
271
+ def forget_attribute_assignments
272
+ @attributes = @attributes.map(&:forgetting_assignment) if defined?(@attributes)
273
+ end
274
+
275
+ def mutations_before_last_save
276
+ @mutations_before_last_save ||= ActiveModel::NullMutationTracker.instance
277
+ end
278
+
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)
300
+ end
301
+
302
+ # Handles <tt>*_change</tt> for +method_missing+.
303
+ def attribute_change(attr)
304
+ [changed_attributes[attr], _read_attribute(attr)] if attribute_changed?(attr)
305
+ end
306
+
307
+ # Handles <tt>*_previous_change</tt> for +method_missing+.
308
+ def attribute_previous_change(attr)
309
+ previous_changes[attr] if attribute_previously_changed?(attr)
310
+ end
311
+
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)
322
+ 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
+ end
342
+ end
343
+ end