activemodel 5.2.2.1 → 6.0.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +144 -51
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -2
- data/lib/active_model.rb +1 -1
- data/lib/active_model/attribute.rb +3 -4
- data/lib/active_model/attribute/user_provided_default.rb +1 -2
- data/lib/active_model/attribute_assignment.rb +1 -1
- data/lib/active_model/attribute_methods.rb +54 -15
- data/lib/active_model/attribute_mutation_tracker.rb +88 -34
- data/lib/active_model/attribute_set.rb +2 -10
- data/lib/active_model/attribute_set/builder.rb +1 -3
- data/lib/active_model/attribute_set/yaml_encoder.rb +1 -2
- data/lib/active_model/attributes.rb +60 -33
- data/lib/active_model/callbacks.rb +10 -7
- data/lib/active_model/conversion.rb +1 -1
- data/lib/active_model/dirty.rb +36 -99
- data/lib/active_model/errors.rb +104 -20
- data/lib/active_model/gem_version.rb +3 -3
- data/lib/active_model/naming.rb +19 -3
- data/lib/active_model/railtie.rb +6 -0
- data/lib/active_model/secure_password.rb +47 -48
- data/lib/active_model/serializers/json.rb +10 -9
- data/lib/active_model/type/binary.rb +1 -1
- data/lib/active_model/type/boolean.rb +10 -1
- data/lib/active_model/type/date.rb +2 -5
- data/lib/active_model/type/date_time.rb +4 -7
- data/lib/active_model/type/float.rb +0 -2
- data/lib/active_model/type/helpers.rb +1 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +4 -0
- data/lib/active_model/type/helpers/numeric.rb +9 -2
- data/lib/active_model/type/helpers/time_value.rb +16 -15
- data/lib/active_model/type/helpers/timezone.rb +19 -0
- data/lib/active_model/type/integer.rb +7 -19
- data/lib/active_model/type/registry.rb +2 -10
- data/lib/active_model/type/string.rb +2 -2
- data/lib/active_model/type/time.rb +2 -1
- data/lib/active_model/validations.rb +0 -2
- data/lib/active_model/validations/acceptance.rb +33 -25
- data/lib/active_model/validations/clusivity.rb +1 -1
- data/lib/active_model/validations/confirmation.rb +2 -2
- data/lib/active_model/validations/inclusion.rb +1 -1
- data/lib/active_model/validations/length.rb +1 -1
- data/lib/active_model/validations/numericality.rb +20 -11
- data/lib/active_model/validations/validates.rb +2 -2
- data/lib/active_model/validator.rb +1 -1
- metadata +13 -9
@@ -1,14 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/core_ext/hash/indifferent_access"
|
4
|
+
require "active_support/core_ext/object/duplicable"
|
4
5
|
|
5
6
|
module ActiveModel
|
6
7
|
class AttributeMutationTracker # :nodoc:
|
7
8
|
OPTION_NOT_GIVEN = Object.new
|
8
9
|
|
9
|
-
def initialize(attributes)
|
10
|
+
def initialize(attributes, forced_changes = Set.new)
|
10
11
|
@attributes = attributes
|
11
|
-
@forced_changes =
|
12
|
+
@forced_changes = forced_changes
|
12
13
|
end
|
13
14
|
|
14
15
|
def changed_attribute_names
|
@@ -18,24 +19,22 @@ module ActiveModel
|
|
18
19
|
def changed_values
|
19
20
|
attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
|
20
21
|
if changed?(attr_name)
|
21
|
-
result[attr_name] =
|
22
|
+
result[attr_name] = original_value(attr_name)
|
22
23
|
end
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
26
27
|
def changes
|
27
28
|
attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
|
28
|
-
change = change_to_attribute(attr_name)
|
29
|
-
if change
|
29
|
+
if change = change_to_attribute(attr_name)
|
30
30
|
result.merge!(attr_name => change)
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
35
|
def change_to_attribute(attr_name)
|
36
|
-
attr_name = attr_name.to_s
|
37
36
|
if changed?(attr_name)
|
38
|
-
[
|
37
|
+
[original_value(attr_name), fetch_value(attr_name)]
|
39
38
|
end
|
40
39
|
end
|
41
40
|
|
@@ -44,81 +43,136 @@ module ActiveModel
|
|
44
43
|
end
|
45
44
|
|
46
45
|
def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN)
|
47
|
-
attr_name
|
48
|
-
|
49
|
-
|
50
|
-
(OPTION_NOT_GIVEN == from || attributes[attr_name].original_value == from) &&
|
51
|
-
(OPTION_NOT_GIVEN == to || attributes[attr_name].value == to)
|
46
|
+
attribute_changed?(attr_name) &&
|
47
|
+
(OPTION_NOT_GIVEN == from || original_value(attr_name) == from) &&
|
48
|
+
(OPTION_NOT_GIVEN == to || fetch_value(attr_name) == to)
|
52
49
|
end
|
53
50
|
|
54
51
|
def changed_in_place?(attr_name)
|
55
|
-
attributes[attr_name
|
52
|
+
attributes[attr_name].changed_in_place?
|
56
53
|
end
|
57
54
|
|
58
55
|
def forget_change(attr_name)
|
59
|
-
attr_name = attr_name.to_s
|
60
56
|
attributes[attr_name] = attributes[attr_name].forgetting_assignment
|
61
57
|
forced_changes.delete(attr_name)
|
62
58
|
end
|
63
59
|
|
64
60
|
def original_value(attr_name)
|
65
|
-
attributes[attr_name
|
61
|
+
attributes[attr_name].original_value
|
66
62
|
end
|
67
63
|
|
68
64
|
def force_change(attr_name)
|
69
|
-
forced_changes << attr_name
|
65
|
+
forced_changes << attr_name
|
70
66
|
end
|
71
67
|
|
72
|
-
|
73
|
-
# Workaround for Ruby 2.2 "private attribute?" warning.
|
74
|
-
protected
|
75
|
-
|
68
|
+
private
|
76
69
|
attr_reader :attributes, :forced_changes
|
77
70
|
|
71
|
+
def attr_names
|
72
|
+
attributes.keys
|
73
|
+
end
|
74
|
+
|
75
|
+
def attribute_changed?(attr_name)
|
76
|
+
forced_changes.include?(attr_name) || !!attributes[attr_name].changed?
|
77
|
+
end
|
78
|
+
|
79
|
+
def fetch_value(attr_name)
|
80
|
+
attributes.fetch_value(attr_name)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class ForcedMutationTracker < AttributeMutationTracker # :nodoc:
|
85
|
+
def initialize(attributes, forced_changes = {})
|
86
|
+
super
|
87
|
+
@finalized_changes = nil
|
88
|
+
end
|
89
|
+
|
90
|
+
def changed_in_place?(attr_name)
|
91
|
+
false
|
92
|
+
end
|
93
|
+
|
94
|
+
def change_to_attribute(attr_name)
|
95
|
+
if finalized_changes&.include?(attr_name)
|
96
|
+
finalized_changes[attr_name].dup
|
97
|
+
else
|
98
|
+
super
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def forget_change(attr_name)
|
103
|
+
forced_changes.delete(attr_name)
|
104
|
+
end
|
105
|
+
|
106
|
+
def original_value(attr_name)
|
107
|
+
if changed?(attr_name)
|
108
|
+
forced_changes[attr_name]
|
109
|
+
else
|
110
|
+
fetch_value(attr_name)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def force_change(attr_name)
|
115
|
+
forced_changes[attr_name] = clone_value(attr_name) unless attribute_changed?(attr_name)
|
116
|
+
end
|
117
|
+
|
118
|
+
def finalize_changes
|
119
|
+
@finalized_changes = changes
|
120
|
+
end
|
121
|
+
|
78
122
|
private
|
123
|
+
attr_reader :finalized_changes
|
79
124
|
|
80
125
|
def attr_names
|
81
|
-
|
126
|
+
forced_changes.keys
|
127
|
+
end
|
128
|
+
|
129
|
+
def attribute_changed?(attr_name)
|
130
|
+
forced_changes.include?(attr_name)
|
131
|
+
end
|
132
|
+
|
133
|
+
def fetch_value(attr_name)
|
134
|
+
attributes.send(:_read_attribute, attr_name)
|
135
|
+
end
|
136
|
+
|
137
|
+
def clone_value(attr_name)
|
138
|
+
value = fetch_value(attr_name)
|
139
|
+
value.duplicable? ? value.clone : value
|
140
|
+
rescue TypeError, NoMethodError
|
141
|
+
value
|
82
142
|
end
|
83
143
|
end
|
84
144
|
|
85
145
|
class NullMutationTracker # :nodoc:
|
86
146
|
include Singleton
|
87
147
|
|
88
|
-
def changed_attribute_names
|
148
|
+
def changed_attribute_names
|
89
149
|
[]
|
90
150
|
end
|
91
151
|
|
92
|
-
def changed_values
|
152
|
+
def changed_values
|
93
153
|
{}
|
94
154
|
end
|
95
155
|
|
96
|
-
def changes
|
156
|
+
def changes
|
97
157
|
{}
|
98
158
|
end
|
99
159
|
|
100
160
|
def change_to_attribute(attr_name)
|
101
161
|
end
|
102
162
|
|
103
|
-
def any_changes?
|
163
|
+
def any_changes?
|
104
164
|
false
|
105
165
|
end
|
106
166
|
|
107
|
-
def changed?(
|
167
|
+
def changed?(attr_name, **)
|
108
168
|
false
|
109
169
|
end
|
110
170
|
|
111
|
-
def changed_in_place?(
|
171
|
+
def changed_in_place?(attr_name)
|
112
172
|
false
|
113
173
|
end
|
114
174
|
|
115
|
-
def
|
116
|
-
end
|
117
|
-
|
118
|
-
def original_value(*)
|
119
|
-
end
|
120
|
-
|
121
|
-
def force_change(*)
|
175
|
+
def original_value(attr_name)
|
122
176
|
end
|
123
177
|
end
|
124
178
|
end
|
@@ -37,16 +37,8 @@ module ActiveModel
|
|
37
37
|
attributes.each_key.select { |name| self[name].initialized? }
|
38
38
|
end
|
39
39
|
|
40
|
-
|
41
|
-
|
42
|
-
# https://github.com/jruby/jruby/pull/2562
|
43
|
-
def fetch_value(name, &block)
|
44
|
-
self[name].value(&block)
|
45
|
-
end
|
46
|
-
else
|
47
|
-
def fetch_value(name)
|
48
|
-
self[name].value { |n| yield n if block_given? }
|
49
|
-
end
|
40
|
+
def fetch_value(name, &block)
|
41
|
+
self[name].value(&block)
|
50
42
|
end
|
51
43
|
|
52
44
|
def write_from_database(name, value)
|
@@ -90,9 +90,6 @@ module ActiveModel
|
|
90
90
|
end
|
91
91
|
|
92
92
|
protected
|
93
|
-
|
94
|
-
attr_reader :types, :values, :additional_types, :delegate_hash, :default_attributes
|
95
|
-
|
96
93
|
def materialize
|
97
94
|
unless @materialized
|
98
95
|
values.each_key { |key| self[key] }
|
@@ -105,6 +102,7 @@ module ActiveModel
|
|
105
102
|
end
|
106
103
|
|
107
104
|
private
|
105
|
+
attr_reader :types, :values, :additional_types, :delegate_hash, :default_attributes
|
108
106
|
|
109
107
|
def assign_default_value(name)
|
110
108
|
type = additional_types.fetch(name, types[name])
|
@@ -26,20 +26,34 @@ module ActiveModel
|
|
26
26
|
define_attribute_method(name)
|
27
27
|
end
|
28
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
|
+
|
29
44
|
private
|
30
45
|
|
31
46
|
def define_method_attribute=(name)
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
STR
|
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
|
43
57
|
end
|
44
58
|
|
45
59
|
NO_DEFAULT_PROVIDED = Object.new # :nodoc:
|
@@ -66,46 +80,59 @@ module ActiveModel
|
|
66
80
|
super
|
67
81
|
end
|
68
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}
|
69
96
|
def attributes
|
70
97
|
@attributes.to_hash
|
71
98
|
end
|
72
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
|
+
|
73
116
|
private
|
74
117
|
|
75
118
|
def write_attribute(attr_name, value)
|
76
|
-
name =
|
77
|
-
|
78
|
-
else
|
79
|
-
attr_name.to_s
|
80
|
-
end
|
119
|
+
name = attr_name.to_s
|
120
|
+
name = self.class.attribute_aliases[name] || name
|
81
121
|
|
82
122
|
@attributes.write_from_user(name, value)
|
83
123
|
value
|
84
124
|
end
|
85
125
|
|
86
126
|
def attribute(attr_name)
|
87
|
-
name =
|
88
|
-
|
89
|
-
|
90
|
-
attr_name.to_s
|
91
|
-
end
|
127
|
+
name = attr_name.to_s
|
128
|
+
name = self.class.attribute_aliases[name] || name
|
129
|
+
|
92
130
|
@attributes.fetch_value(name)
|
93
131
|
end
|
94
132
|
|
95
|
-
#
|
133
|
+
# Dispatch target for <tt>*=</tt> attribute methods.
|
96
134
|
def attribute=(attribute_name, value)
|
97
135
|
write_attribute(attribute_name, value)
|
98
136
|
end
|
99
137
|
end
|
100
|
-
|
101
|
-
module AttributeMethods #:nodoc:
|
102
|
-
AttrNames = Module.new {
|
103
|
-
def self.set_name_cache(name, value)
|
104
|
-
const_name = "ATTR_#{name}"
|
105
|
-
unless const_defined? const_name
|
106
|
-
const_set const_name, value.dup.freeze
|
107
|
-
end
|
108
|
-
end
|
109
|
-
}
|
110
|
-
end
|
111
138
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/core_ext/array/extract_options"
|
4
|
+
require "active_support/core_ext/hash/keys"
|
4
5
|
|
5
6
|
module ActiveModel
|
6
7
|
# == Active \Model \Callbacks
|
@@ -127,26 +128,28 @@ module ActiveModel
|
|
127
128
|
private
|
128
129
|
|
129
130
|
def _define_before_model_callback(klass, callback)
|
130
|
-
klass.define_singleton_method("before_#{callback}") do |*args, &block|
|
131
|
-
|
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)
|
132
134
|
end
|
133
135
|
end
|
134
136
|
|
135
137
|
def _define_around_model_callback(klass, callback)
|
136
|
-
klass.define_singleton_method("around_#{callback}") do |*args, &block|
|
137
|
-
|
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)
|
138
141
|
end
|
139
142
|
end
|
140
143
|
|
141
144
|
def _define_after_model_callback(klass, callback)
|
142
|
-
klass.define_singleton_method("after_#{callback}") do |*args, &block|
|
143
|
-
options
|
145
|
+
klass.define_singleton_method("after_#{callback}") do |*args, **options, &block|
|
146
|
+
options.assert_valid_keys(:if, :unless, :prepend)
|
144
147
|
options[:prepend] = true
|
145
148
|
conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v|
|
146
149
|
v != false
|
147
150
|
}
|
148
151
|
options[:if] = Array(options[:if]) << conditional
|
149
|
-
set_callback(:"#{callback}", :after, *
|
152
|
+
set_callback(:"#{callback}", :after, *args, options, &block)
|
150
153
|
end
|
151
154
|
end
|
152
155
|
end
|
@@ -103,7 +103,7 @@ module ActiveModel
|
|
103
103
|
@_to_partial_path ||= begin
|
104
104
|
element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name))
|
105
105
|
collection = ActiveSupport::Inflector.tableize(name)
|
106
|
-
"#{collection}/#{element}"
|
106
|
+
"#{collection}/#{element}"
|
107
107
|
end
|
108
108
|
end
|
109
109
|
end
|
data/lib/active_model/dirty.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/hash_with_indifferent_access"
|
4
|
-
require "active_support/core_ext/object/duplicable"
|
5
3
|
require "active_model/attribute_mutation_tracker"
|
6
4
|
|
7
5
|
module ActiveModel
|
@@ -122,9 +120,6 @@ module ActiveModel
|
|
122
120
|
extend ActiveSupport::Concern
|
123
121
|
include ActiveModel::AttributeMethods
|
124
122
|
|
125
|
-
OPTION_NOT_GIVEN = Object.new # :nodoc:
|
126
|
-
private_constant :OPTION_NOT_GIVEN
|
127
|
-
|
128
123
|
included do
|
129
124
|
attribute_method_suffix "_changed?", "_change", "_will_change!", "_was"
|
130
125
|
attribute_method_suffix "_previously_changed?", "_previous_change"
|
@@ -145,21 +140,20 @@ module ActiveModel
|
|
145
140
|
# +mutations_from_database+ to +mutations_before_last_save+ respectively.
|
146
141
|
def changes_applied
|
147
142
|
unless defined?(@attributes)
|
148
|
-
|
143
|
+
mutations_from_database.finalize_changes
|
149
144
|
end
|
150
145
|
@mutations_before_last_save = mutations_from_database
|
151
|
-
@attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
|
152
146
|
forget_attribute_assignments
|
153
147
|
@mutations_from_database = nil
|
154
148
|
end
|
155
149
|
|
156
|
-
# Returns +true+ if any of the attributes
|
150
|
+
# Returns +true+ if any of the attributes has unsaved changes, +false+ otherwise.
|
157
151
|
#
|
158
152
|
# person.changed? # => false
|
159
153
|
# person.name = 'bob'
|
160
154
|
# person.changed? # => true
|
161
155
|
def changed?
|
162
|
-
|
156
|
+
mutations_from_database.any_changes?
|
163
157
|
end
|
164
158
|
|
165
159
|
# Returns an array with the name of the attributes with unsaved changes.
|
@@ -168,42 +162,37 @@ module ActiveModel
|
|
168
162
|
# person.name = 'bob'
|
169
163
|
# person.changed # => ["name"]
|
170
164
|
def changed
|
171
|
-
|
165
|
+
mutations_from_database.changed_attribute_names
|
172
166
|
end
|
173
167
|
|
174
|
-
#
|
175
|
-
def attribute_changed?(
|
176
|
-
|
177
|
-
(to == OPTION_NOT_GIVEN || to == _read_attribute(attr)) &&
|
178
|
-
(from == OPTION_NOT_GIVEN || from == changed_attributes[attr])
|
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)
|
179
171
|
end
|
180
172
|
|
181
|
-
#
|
182
|
-
def attribute_was(
|
183
|
-
|
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)
|
184
176
|
end
|
185
177
|
|
186
|
-
#
|
187
|
-
def attribute_previously_changed?(
|
188
|
-
|
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)
|
189
181
|
end
|
190
182
|
|
191
183
|
# Restore all previous data of the provided attributes.
|
192
|
-
def restore_attributes(
|
193
|
-
|
184
|
+
def restore_attributes(attr_names = changed)
|
185
|
+
attr_names.each { |attr_name| restore_attribute!(attr_name) }
|
194
186
|
end
|
195
187
|
|
196
188
|
# Clears all dirty data: current changes and previous changes.
|
197
189
|
def clear_changes_information
|
198
|
-
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new
|
199
190
|
@mutations_before_last_save = nil
|
200
|
-
@attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
|
201
191
|
forget_attribute_assignments
|
202
192
|
@mutations_from_database = nil
|
203
193
|
end
|
204
194
|
|
205
195
|
def clear_attribute_changes(attr_names)
|
206
|
-
attributes_changed_by_setter.except!(*attr_names)
|
207
196
|
attr_names.each do |attr_name|
|
208
197
|
clear_attribute_change(attr_name)
|
209
198
|
end
|
@@ -216,13 +205,7 @@ module ActiveModel
|
|
216
205
|
# person.name = 'robert'
|
217
206
|
# person.changed_attributes # => {"name" => "bob"}
|
218
207
|
def changed_attributes
|
219
|
-
|
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
|
208
|
+
mutations_from_database.changed_values
|
226
209
|
end
|
227
210
|
|
228
211
|
# Returns a hash of changed attributes indicating their original
|
@@ -232,9 +215,7 @@ module ActiveModel
|
|
232
215
|
# person.name = 'bob'
|
233
216
|
# person.changes # => { "name" => ["bill", "bob"] }
|
234
217
|
def changes
|
235
|
-
|
236
|
-
ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
|
237
|
-
end
|
218
|
+
mutations_from_database.changes
|
238
219
|
end
|
239
220
|
|
240
221
|
# Returns a hash of attributes that were changed before the model was saved.
|
@@ -244,27 +225,23 @@ module ActiveModel
|
|
244
225
|
# person.save
|
245
226
|
# person.previous_changes # => {"name" => ["bob", "robert"]}
|
246
227
|
def previous_changes
|
247
|
-
|
248
|
-
@previously_changed.merge(mutations_before_last_save.changes)
|
228
|
+
mutations_before_last_save.changes
|
249
229
|
end
|
250
230
|
|
251
231
|
def attribute_changed_in_place?(attr_name) # :nodoc:
|
252
|
-
mutations_from_database.changed_in_place?(attr_name)
|
232
|
+
mutations_from_database.changed_in_place?(attr_name.to_s)
|
253
233
|
end
|
254
234
|
|
255
235
|
private
|
256
236
|
def clear_attribute_change(attr_name)
|
257
|
-
mutations_from_database.forget_change(attr_name)
|
237
|
+
mutations_from_database.forget_change(attr_name.to_s)
|
258
238
|
end
|
259
239
|
|
260
240
|
def mutations_from_database
|
261
|
-
unless defined?(@mutations_from_database)
|
262
|
-
@mutations_from_database = nil
|
263
|
-
end
|
264
241
|
@mutations_from_database ||= if defined?(@attributes)
|
265
242
|
ActiveModel::AttributeMutationTracker.new(@attributes)
|
266
243
|
else
|
267
|
-
|
244
|
+
ActiveModel::ForcedMutationTracker.new(self)
|
268
245
|
end
|
269
246
|
end
|
270
247
|
|
@@ -276,68 +253,28 @@ module ActiveModel
|
|
276
253
|
@mutations_before_last_save ||= ActiveModel::NullMutationTracker.instance
|
277
254
|
end
|
278
255
|
|
279
|
-
|
280
|
-
|
281
|
-
|
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)
|
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)
|
305
259
|
end
|
306
260
|
|
307
|
-
#
|
308
|
-
def attribute_previous_change(
|
309
|
-
|
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)
|
310
264
|
end
|
311
265
|
|
312
|
-
#
|
313
|
-
def attribute_will_change!(
|
314
|
-
|
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)
|
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)
|
324
269
|
end
|
325
270
|
|
326
|
-
#
|
327
|
-
def restore_attribute!(
|
328
|
-
|
329
|
-
|
330
|
-
|
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)
|
331
277
|
end
|
332
278
|
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
279
|
end
|
343
280
|
end
|