activemodel 5.2.3

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