activemodel 6.0.0

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