activemodel 6.0.0

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 +172 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +266 -0
  5. data/lib/active_model.rb +77 -0
  6. data/lib/active_model/attribute.rb +247 -0
  7. data/lib/active_model/attribute/user_provided_default.rb +51 -0
  8. data/lib/active_model/attribute_assignment.rb +57 -0
  9. data/lib/active_model/attribute_methods.rb +517 -0
  10. data/lib/active_model/attribute_mutation_tracker.rb +178 -0
  11. data/lib/active_model/attribute_set.rb +106 -0
  12. data/lib/active_model/attribute_set/builder.rb +124 -0
  13. data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
  14. data/lib/active_model/attributes.rb +138 -0
  15. data/lib/active_model/callbacks.rb +156 -0
  16. data/lib/active_model/conversion.rb +111 -0
  17. data/lib/active_model/dirty.rb +280 -0
  18. data/lib/active_model/errors.rb +601 -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 +334 -0
  25. data/lib/active_model/railtie.rb +20 -0
  26. data/lib/active_model/secure_password.rb +128 -0
  27. data/lib/active_model/serialization.rb +192 -0
  28. data/lib/active_model/serializers/json.rb +147 -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 +47 -0
  34. data/lib/active_model/type/date.rb +53 -0
  35. data/lib/active_model/type/date_time.rb +47 -0
  36. data/lib/active_model/type/decimal.rb +70 -0
  37. data/lib/active_model/type/float.rb +34 -0
  38. data/lib/active_model/type/helpers.rb +7 -0
  39. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +45 -0
  40. data/lib/active_model/type/helpers/mutable.rb +20 -0
  41. data/lib/active_model/type/helpers/numeric.rb +44 -0
  42. data/lib/active_model/type/helpers/time_value.rb +81 -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 +58 -0
  46. data/lib/active_model/type/registry.rb +62 -0
  47. data/lib/active_model/type/string.rb +26 -0
  48. data/lib/active_model/type/time.rb +47 -0
  49. data/lib/active_model/type/value.rb +126 -0
  50. data/lib/active_model/validations.rb +437 -0
  51. data/lib/active_model/validations/absence.rb +33 -0
  52. data/lib/active_model/validations/acceptance.rb +102 -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,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/attribute_set"
4
+ require "active_model/attribute/user_provided_default"
5
+
6
+ module ActiveModel
7
+ module Attributes #:nodoc:
8
+ extend ActiveSupport::Concern
9
+ include ActiveModel::AttributeMethods
10
+
11
+ included do
12
+ attribute_method_suffix "="
13
+ class_attribute :attribute_types, :_default_attributes, instance_accessor: false
14
+ self.attribute_types = Hash.new(Type.default_value)
15
+ self._default_attributes = AttributeSet.new({})
16
+ end
17
+
18
+ module ClassMethods
19
+ def attribute(name, type = Type::Value.new, **options)
20
+ name = name.to_s
21
+ if type.is_a?(Symbol)
22
+ type = ActiveModel::Type.lookup(type, **options.except(:default))
23
+ end
24
+ self.attribute_types = attribute_types.merge(name => type)
25
+ define_default_attribute(name, options.fetch(:default, NO_DEFAULT_PROVIDED), type)
26
+ define_attribute_method(name)
27
+ end
28
+
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
43
+
44
+ private
45
+
46
+ def define_method_attribute=(name)
47
+ ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
48
+ generated_attribute_methods, name, writer: true,
49
+ ) do |temp_method_name, attr_name_expr|
50
+ generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
51
+ def #{temp_method_name}(value)
52
+ name = #{attr_name_expr}
53
+ write_attribute(name, value)
54
+ end
55
+ RUBY
56
+ end
57
+ end
58
+
59
+ NO_DEFAULT_PROVIDED = Object.new # :nodoc:
60
+ private_constant :NO_DEFAULT_PROVIDED
61
+
62
+ def define_default_attribute(name, value, type)
63
+ self._default_attributes = _default_attributes.deep_dup
64
+ if value == NO_DEFAULT_PROVIDED
65
+ default_attribute = _default_attributes[name].with_type(type)
66
+ else
67
+ default_attribute = Attribute::UserProvidedDefault.new(
68
+ name,
69
+ value,
70
+ type,
71
+ _default_attributes.fetch(name.to_s) { nil },
72
+ )
73
+ end
74
+ _default_attributes[name] = default_attribute
75
+ end
76
+ end
77
+
78
+ def initialize(*)
79
+ @attributes = self.class._default_attributes.deep_dup
80
+ super
81
+ end
82
+
83
+ # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
84
+ #
85
+ # class Person
86
+ # include ActiveModel::Model
87
+ # include ActiveModel::Attributes
88
+ #
89
+ # attribute :name, :string
90
+ # attribute :age, :integer
91
+ # end
92
+ #
93
+ # person = Person.new(name: 'Francesco', age: 22)
94
+ # person.attributes
95
+ # # => {"name"=>"Francesco", "age"=>22}
96
+ def attributes
97
+ @attributes.to_hash
98
+ end
99
+
100
+ # Returns an array of attribute names as strings
101
+ #
102
+ # class Person
103
+ # include ActiveModel::Attributes
104
+ #
105
+ # attribute :name, :string
106
+ # attribute :age, :integer
107
+ # end
108
+ #
109
+ # person = Person.new
110
+ # person.attribute_names
111
+ # # => ["name", "age"]
112
+ def attribute_names
113
+ @attributes.keys
114
+ end
115
+
116
+ private
117
+
118
+ def write_attribute(attr_name, value)
119
+ name = attr_name.to_s
120
+ name = self.class.attribute_aliases[name] || name
121
+
122
+ @attributes.write_from_user(name, value)
123
+ value
124
+ end
125
+
126
+ def attribute(attr_name)
127
+ name = attr_name.to_s
128
+ name = self.class.attribute_aliases[name] || name
129
+
130
+ @attributes.fetch_value(name)
131
+ end
132
+
133
+ # Dispatch target for <tt>*=</tt> attribute methods.
134
+ def attribute=(attribute_name, value)
135
+ write_attribute(attribute_name, value)
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/array/extract_options"
4
+ require "active_support/core_ext/hash/keys"
5
+
6
+ module ActiveModel
7
+ # == Active \Model \Callbacks
8
+ #
9
+ # Provides an interface for any class to have Active Record like callbacks.
10
+ #
11
+ # Like the Active Record methods, the callback chain is aborted as soon as
12
+ # one of the methods throws +:abort+.
13
+ #
14
+ # First, extend ActiveModel::Callbacks from the class you are creating:
15
+ #
16
+ # class MyModel
17
+ # extend ActiveModel::Callbacks
18
+ # end
19
+ #
20
+ # Then define a list of methods that you want callbacks attached to:
21
+ #
22
+ # define_model_callbacks :create, :update
23
+ #
24
+ # This will provide all three standard callbacks (before, around and after)
25
+ # for both the <tt>:create</tt> and <tt>:update</tt> methods. To implement,
26
+ # you need to wrap the methods you want callbacks on in a block so that the
27
+ # callbacks get a chance to fire:
28
+ #
29
+ # def create
30
+ # run_callbacks :create do
31
+ # # Your create action methods here
32
+ # end
33
+ # end
34
+ #
35
+ # Then in your class, you can use the +before_create+, +after_create+ and
36
+ # +around_create+ methods, just as you would in an Active Record model.
37
+ #
38
+ # before_create :action_before_create
39
+ #
40
+ # def action_before_create
41
+ # # Your code here
42
+ # end
43
+ #
44
+ # When defining an around callback remember to yield to the block, otherwise
45
+ # it won't be executed:
46
+ #
47
+ # around_create :log_status
48
+ #
49
+ # def log_status
50
+ # puts 'going to call the block...'
51
+ # yield
52
+ # puts 'block successfully called.'
53
+ # end
54
+ #
55
+ # You can choose to have only specific callbacks by passing a hash to the
56
+ # +define_model_callbacks+ method.
57
+ #
58
+ # define_model_callbacks :create, only: [:after, :before]
59
+ #
60
+ # Would only create the +after_create+ and +before_create+ callback methods in
61
+ # your class.
62
+ #
63
+ # NOTE: Calling the same callback multiple times will overwrite previous callback definitions.
64
+ #
65
+ module Callbacks
66
+ def self.extended(base) #:nodoc:
67
+ base.class_eval do
68
+ include ActiveSupport::Callbacks
69
+ end
70
+ end
71
+
72
+ # define_model_callbacks accepts the same options +define_callbacks+ does,
73
+ # in case you want to overwrite a default. Besides that, it also accepts an
74
+ # <tt>:only</tt> option, where you can choose if you want all types (before,
75
+ # around or after) or just some.
76
+ #
77
+ # define_model_callbacks :initializer, only: :after
78
+ #
79
+ # Note, the <tt>only: <type></tt> hash will apply to all callbacks defined
80
+ # on that method call. To get around this you can call the define_model_callbacks
81
+ # method as many times as you need.
82
+ #
83
+ # define_model_callbacks :create, only: :after
84
+ # define_model_callbacks :update, only: :before
85
+ # define_model_callbacks :destroy, only: :around
86
+ #
87
+ # Would create +after_create+, +before_update+ and +around_destroy+ methods
88
+ # only.
89
+ #
90
+ # You can pass in a class to before_<type>, after_<type> and around_<type>,
91
+ # in which case the callback will call that class's <action>_<type> method
92
+ # passing the object that the callback is being called on.
93
+ #
94
+ # class MyModel
95
+ # extend ActiveModel::Callbacks
96
+ # define_model_callbacks :create
97
+ #
98
+ # before_create AnotherClass
99
+ # end
100
+ #
101
+ # class AnotherClass
102
+ # def self.before_create( obj )
103
+ # # obj is the MyModel instance that the callback is being called on
104
+ # end
105
+ # end
106
+ #
107
+ # NOTE: +method_name+ passed to define_model_callbacks must not end with
108
+ # <tt>!</tt>, <tt>?</tt> or <tt>=</tt>.
109
+ def define_model_callbacks(*callbacks)
110
+ options = callbacks.extract_options!
111
+ options = {
112
+ skip_after_callbacks_if_terminated: true,
113
+ scope: [:kind, :name],
114
+ only: [:before, :around, :after]
115
+ }.merge!(options)
116
+
117
+ types = Array(options.delete(:only))
118
+
119
+ callbacks.each do |callback|
120
+ define_callbacks(callback, options)
121
+
122
+ types.each do |type|
123
+ send("_define_#{type}_model_callback", self, callback)
124
+ end
125
+ end
126
+ end
127
+
128
+ private
129
+
130
+ def _define_before_model_callback(klass, callback)
131
+ klass.define_singleton_method("before_#{callback}") do |*args, **options, &block|
132
+ options.assert_valid_keys(:if, :unless, :prepend)
133
+ set_callback(:"#{callback}", :before, *args, options, &block)
134
+ end
135
+ end
136
+
137
+ def _define_around_model_callback(klass, callback)
138
+ klass.define_singleton_method("around_#{callback}") do |*args, **options, &block|
139
+ options.assert_valid_keys(:if, :unless, :prepend)
140
+ set_callback(:"#{callback}", :around, *args, options, &block)
141
+ end
142
+ end
143
+
144
+ def _define_after_model_callback(klass, callback)
145
+ klass.define_singleton_method("after_#{callback}") do |*args, **options, &block|
146
+ options.assert_valid_keys(:if, :unless, :prepend)
147
+ options[:prepend] = true
148
+ conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v|
149
+ v != false
150
+ }
151
+ options[:if] = Array(options[:if]) << conditional
152
+ set_callback(:"#{callback}", :after, *args, options, &block)
153
+ end
154
+ end
155
+ end
156
+ 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}"
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,280 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/attribute_mutation_tracker"
4
+
5
+ module ActiveModel
6
+ # == Active \Model \Dirty
7
+ #
8
+ # Provides a way to track changes in your object in the same way as
9
+ # Active Record does.
10
+ #
11
+ # The requirements for implementing ActiveModel::Dirty are:
12
+ #
13
+ # * <tt>include ActiveModel::Dirty</tt> in your object.
14
+ # * Call <tt>define_attribute_methods</tt> passing each method you want to
15
+ # track.
16
+ # * Call <tt>[attr_name]_will_change!</tt> before each change to the tracked
17
+ # attribute.
18
+ # * Call <tt>changes_applied</tt> after the changes are persisted.
19
+ # * Call <tt>clear_changes_information</tt> when you want to reset the changes
20
+ # information.
21
+ # * Call <tt>restore_attributes</tt> when you want to restore previous data.
22
+ #
23
+ # A minimal implementation could be:
24
+ #
25
+ # class Person
26
+ # include ActiveModel::Dirty
27
+ #
28
+ # define_attribute_methods :name
29
+ #
30
+ # def initialize
31
+ # @name = nil
32
+ # end
33
+ #
34
+ # def name
35
+ # @name
36
+ # end
37
+ #
38
+ # def name=(val)
39
+ # name_will_change! unless val == @name
40
+ # @name = val
41
+ # end
42
+ #
43
+ # def save
44
+ # # do persistence work
45
+ #
46
+ # changes_applied
47
+ # end
48
+ #
49
+ # def reload!
50
+ # # get the values from the persistence layer
51
+ #
52
+ # clear_changes_information
53
+ # end
54
+ #
55
+ # def rollback!
56
+ # restore_attributes
57
+ # end
58
+ # end
59
+ #
60
+ # A newly instantiated +Person+ object is unchanged:
61
+ #
62
+ # person = Person.new
63
+ # person.changed? # => false
64
+ #
65
+ # Change the name:
66
+ #
67
+ # person.name = 'Bob'
68
+ # person.changed? # => true
69
+ # person.name_changed? # => true
70
+ # person.name_changed?(from: nil, to: "Bob") # => true
71
+ # person.name_was # => nil
72
+ # person.name_change # => [nil, "Bob"]
73
+ # person.name = 'Bill'
74
+ # person.name_change # => [nil, "Bill"]
75
+ #
76
+ # Save the changes:
77
+ #
78
+ # person.save
79
+ # person.changed? # => false
80
+ # person.name_changed? # => false
81
+ #
82
+ # Reset the changes:
83
+ #
84
+ # person.previous_changes # => {"name" => [nil, "Bill"]}
85
+ # person.name_previously_changed? # => true
86
+ # person.name_previous_change # => [nil, "Bill"]
87
+ # person.reload!
88
+ # person.previous_changes # => {}
89
+ #
90
+ # Rollback the changes:
91
+ #
92
+ # person.name = "Uncle Bob"
93
+ # person.rollback!
94
+ # person.name # => "Bill"
95
+ # person.name_changed? # => false
96
+ #
97
+ # Assigning the same value leaves the attribute unchanged:
98
+ #
99
+ # person.name = 'Bill'
100
+ # person.name_changed? # => false
101
+ # person.name_change # => nil
102
+ #
103
+ # Which attributes have changed?
104
+ #
105
+ # person.name = 'Bob'
106
+ # person.changed # => ["name"]
107
+ # person.changes # => {"name" => ["Bill", "Bob"]}
108
+ #
109
+ # If an attribute is modified in-place then make use of
110
+ # <tt>[attribute_name]_will_change!</tt> to mark that the attribute is changing.
111
+ # Otherwise \Active \Model can't track changes to in-place attributes. Note
112
+ # that Active Record can detect in-place modifications automatically. You do
113
+ # not need to call <tt>[attribute_name]_will_change!</tt> on Active Record models.
114
+ #
115
+ # person.name_will_change!
116
+ # person.name_change # => ["Bill", "Bill"]
117
+ # person.name << 'y'
118
+ # person.name_change # => ["Bill", "Billy"]
119
+ module Dirty
120
+ extend ActiveSupport::Concern
121
+ include ActiveModel::AttributeMethods
122
+
123
+ included do
124
+ attribute_method_suffix "_changed?", "_change", "_will_change!", "_was"
125
+ attribute_method_suffix "_previously_changed?", "_previous_change"
126
+ attribute_method_affix prefix: "restore_", suffix: "!"
127
+ end
128
+
129
+ def initialize_dup(other) # :nodoc:
130
+ super
131
+ if self.class.respond_to?(:_default_attributes)
132
+ @attributes = self.class._default_attributes.map do |attr|
133
+ attr.with_value_from_user(@attributes.fetch_value(attr.name))
134
+ end
135
+ end
136
+ @mutations_from_database = nil
137
+ end
138
+
139
+ # Clears dirty data and moves +changes+ to +previously_changed+ and
140
+ # +mutations_from_database+ to +mutations_before_last_save+ respectively.
141
+ def changes_applied
142
+ unless defined?(@attributes)
143
+ mutations_from_database.finalize_changes
144
+ end
145
+ @mutations_before_last_save = mutations_from_database
146
+ forget_attribute_assignments
147
+ @mutations_from_database = nil
148
+ end
149
+
150
+ # Returns +true+ if any of the attributes has unsaved changes, +false+ otherwise.
151
+ #
152
+ # person.changed? # => false
153
+ # person.name = 'bob'
154
+ # person.changed? # => true
155
+ def changed?
156
+ mutations_from_database.any_changes?
157
+ end
158
+
159
+ # Returns an array with the name of the attributes with unsaved changes.
160
+ #
161
+ # person.changed # => []
162
+ # person.name = 'bob'
163
+ # person.changed # => ["name"]
164
+ def changed
165
+ mutations_from_database.changed_attribute_names
166
+ end
167
+
168
+ # Dispatch target for <tt>*_changed?</tt> attribute methods.
169
+ def attribute_changed?(attr_name, **options) # :nodoc:
170
+ mutations_from_database.changed?(attr_name.to_s, options)
171
+ end
172
+
173
+ # Dispatch target for <tt>*_was</tt> attribute methods.
174
+ def attribute_was(attr_name) # :nodoc:
175
+ mutations_from_database.original_value(attr_name.to_s)
176
+ end
177
+
178
+ # Dispatch target for <tt>*_previously_changed?</tt> attribute methods.
179
+ def attribute_previously_changed?(attr_name) # :nodoc:
180
+ mutations_before_last_save.changed?(attr_name.to_s)
181
+ end
182
+
183
+ # Restore all previous data of the provided attributes.
184
+ def restore_attributes(attr_names = changed)
185
+ attr_names.each { |attr_name| restore_attribute!(attr_name) }
186
+ end
187
+
188
+ # Clears all dirty data: current changes and previous changes.
189
+ def clear_changes_information
190
+ @mutations_before_last_save = nil
191
+ forget_attribute_assignments
192
+ @mutations_from_database = nil
193
+ end
194
+
195
+ def clear_attribute_changes(attr_names)
196
+ attr_names.each do |attr_name|
197
+ clear_attribute_change(attr_name)
198
+ end
199
+ end
200
+
201
+ # Returns a hash of the attributes with unsaved changes indicating their original
202
+ # values like <tt>attr => original value</tt>.
203
+ #
204
+ # person.name # => "bob"
205
+ # person.name = 'robert'
206
+ # person.changed_attributes # => {"name" => "bob"}
207
+ def changed_attributes
208
+ mutations_from_database.changed_values
209
+ end
210
+
211
+ # Returns a hash of changed attributes indicating their original
212
+ # and new values like <tt>attr => [original value, new value]</tt>.
213
+ #
214
+ # person.changes # => {}
215
+ # person.name = 'bob'
216
+ # person.changes # => { "name" => ["bill", "bob"] }
217
+ def changes
218
+ mutations_from_database.changes
219
+ end
220
+
221
+ # Returns a hash of attributes that were changed before the model was saved.
222
+ #
223
+ # person.name # => "bob"
224
+ # person.name = 'robert'
225
+ # person.save
226
+ # person.previous_changes # => {"name" => ["bob", "robert"]}
227
+ def previous_changes
228
+ mutations_before_last_save.changes
229
+ end
230
+
231
+ def attribute_changed_in_place?(attr_name) # :nodoc:
232
+ mutations_from_database.changed_in_place?(attr_name.to_s)
233
+ end
234
+
235
+ private
236
+ def clear_attribute_change(attr_name)
237
+ mutations_from_database.forget_change(attr_name.to_s)
238
+ end
239
+
240
+ def mutations_from_database
241
+ @mutations_from_database ||= if defined?(@attributes)
242
+ ActiveModel::AttributeMutationTracker.new(@attributes)
243
+ else
244
+ ActiveModel::ForcedMutationTracker.new(self)
245
+ end
246
+ end
247
+
248
+ def forget_attribute_assignments
249
+ @attributes = @attributes.map(&:forgetting_assignment) if defined?(@attributes)
250
+ end
251
+
252
+ def mutations_before_last_save
253
+ @mutations_before_last_save ||= ActiveModel::NullMutationTracker.instance
254
+ end
255
+
256
+ # Dispatch target for <tt>*_change</tt> attribute methods.
257
+ def attribute_change(attr_name)
258
+ mutations_from_database.change_to_attribute(attr_name.to_s)
259
+ end
260
+
261
+ # Dispatch target for <tt>*_previous_change</tt> attribute methods.
262
+ def attribute_previous_change(attr_name)
263
+ mutations_before_last_save.change_to_attribute(attr_name.to_s)
264
+ end
265
+
266
+ # Dispatch target for <tt>*_will_change!</tt> attribute methods.
267
+ def attribute_will_change!(attr_name)
268
+ mutations_from_database.force_change(attr_name.to_s)
269
+ end
270
+
271
+ # Dispatch target for <tt>restore_*!</tt> attribute methods.
272
+ def restore_attribute!(attr_name)
273
+ attr_name = attr_name.to_s
274
+ if attribute_changed?(attr_name)
275
+ __send__("#{attr_name}=", attribute_was(attr_name))
276
+ clear_attribute_change(attr_name)
277
+ end
278
+ end
279
+ end
280
+ end