activemodel 5.2.6 → 6.1.4
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 +58 -109
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -4
- data/lib/active_model.rb +2 -1
- data/lib/active_model/attribute.rb +21 -21
- data/lib/active_model/attribute/user_provided_default.rb +1 -2
- data/lib/active_model/attribute_assignment.rb +4 -6
- data/lib/active_model/attribute_methods.rb +117 -40
- data/lib/active_model/attribute_mutation_tracker.rb +90 -33
- data/lib/active_model/attribute_set.rb +20 -28
- data/lib/active_model/attribute_set/builder.rb +81 -16
- data/lib/active_model/attribute_set/yaml_encoder.rb +1 -2
- data/lib/active_model/attributes.rb +65 -44
- data/lib/active_model/callbacks.rb +11 -9
- data/lib/active_model/conversion.rb +1 -1
- data/lib/active_model/dirty.rb +51 -101
- data/lib/active_model/error.rb +207 -0
- data/lib/active_model/errors.rb +347 -155
- data/lib/active_model/gem_version.rb +3 -3
- data/lib/active_model/lint.rb +1 -1
- data/lib/active_model/naming.rb +22 -7
- data/lib/active_model/nested_error.rb +22 -0
- data/lib/active_model/railtie.rb +6 -0
- data/lib/active_model/secure_password.rb +54 -55
- data/lib/active_model/serialization.rb +9 -7
- data/lib/active_model/serializers/json.rb +17 -9
- data/lib/active_model/translation.rb +1 -1
- data/lib/active_model/type/big_integer.rb +0 -1
- data/lib/active_model/type/binary.rb +1 -1
- data/lib/active_model/type/boolean.rb +0 -1
- data/lib/active_model/type/date.rb +0 -5
- data/lib/active_model/type/date_time.rb +3 -8
- data/lib/active_model/type/decimal.rb +0 -1
- data/lib/active_model/type/float.rb +2 -3
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +14 -6
- data/lib/active_model/type/helpers/numeric.rb +17 -6
- data/lib/active_model/type/helpers/time_value.rb +37 -15
- data/lib/active_model/type/helpers/timezone.rb +1 -1
- data/lib/active_model/type/immutable_string.rb +14 -11
- data/lib/active_model/type/integer.rb +15 -18
- data/lib/active_model/type/registry.rb +16 -16
- data/lib/active_model/type/string.rb +12 -3
- data/lib/active_model/type/time.rb +1 -6
- data/lib/active_model/type/value.rb +9 -2
- data/lib/active_model/validations.rb +6 -9
- data/lib/active_model/validations/absence.rb +2 -2
- data/lib/active_model/validations/acceptance.rb +34 -27
- data/lib/active_model/validations/callbacks.rb +15 -16
- data/lib/active_model/validations/clusivity.rb +6 -3
- data/lib/active_model/validations/confirmation.rb +4 -4
- data/lib/active_model/validations/exclusion.rb +1 -1
- data/lib/active_model/validations/format.rb +2 -3
- data/lib/active_model/validations/inclusion.rb +2 -2
- data/lib/active_model/validations/length.rb +3 -3
- data/lib/active_model/validations/numericality.rb +58 -44
- data/lib/active_model/validations/presence.rb +1 -1
- data/lib/active_model/validations/validates.rb +7 -6
- data/lib/active_model/validator.rb +8 -3
- metadata +14 -9
@@ -1,6 +1,7 @@
|
|
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:
|
@@ -8,7 +9,6 @@ module ActiveModel
|
|
8
9
|
|
9
10
|
def initialize(attributes)
|
10
11
|
@attributes = attributes
|
11
|
-
@forced_changes = Set.new
|
12
12
|
end
|
13
13
|
|
14
14
|
def changed_attribute_names
|
@@ -18,24 +18,22 @@ module ActiveModel
|
|
18
18
|
def changed_values
|
19
19
|
attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
|
20
20
|
if changed?(attr_name)
|
21
|
-
result[attr_name] =
|
21
|
+
result[attr_name] = original_value(attr_name)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
26
|
def changes
|
27
27
|
attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
|
28
|
-
change = change_to_attribute(attr_name)
|
29
|
-
if change
|
28
|
+
if change = change_to_attribute(attr_name)
|
30
29
|
result.merge!(attr_name => change)
|
31
30
|
end
|
32
31
|
end
|
33
32
|
end
|
34
33
|
|
35
34
|
def change_to_attribute(attr_name)
|
36
|
-
attr_name = attr_name.to_s
|
37
35
|
if changed?(attr_name)
|
38
|
-
[
|
36
|
+
[original_value(attr_name), fetch_value(attr_name)]
|
39
37
|
end
|
40
38
|
end
|
41
39
|
|
@@ -44,81 +42,140 @@ module ActiveModel
|
|
44
42
|
end
|
45
43
|
|
46
44
|
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)
|
45
|
+
attribute_changed?(attr_name) &&
|
46
|
+
(OPTION_NOT_GIVEN == from || original_value(attr_name) == from) &&
|
47
|
+
(OPTION_NOT_GIVEN == to || fetch_value(attr_name) == to)
|
52
48
|
end
|
53
49
|
|
54
50
|
def changed_in_place?(attr_name)
|
55
|
-
attributes[attr_name
|
51
|
+
attributes[attr_name].changed_in_place?
|
56
52
|
end
|
57
53
|
|
58
54
|
def forget_change(attr_name)
|
59
|
-
attr_name = attr_name.to_s
|
60
55
|
attributes[attr_name] = attributes[attr_name].forgetting_assignment
|
61
56
|
forced_changes.delete(attr_name)
|
62
57
|
end
|
63
58
|
|
64
59
|
def original_value(attr_name)
|
65
|
-
attributes[attr_name
|
60
|
+
attributes[attr_name].original_value
|
66
61
|
end
|
67
62
|
|
68
63
|
def force_change(attr_name)
|
69
|
-
forced_changes
|
64
|
+
forced_changes[attr_name] = fetch_value(attr_name)
|
70
65
|
end
|
71
66
|
|
72
|
-
|
73
|
-
|
74
|
-
|
67
|
+
private
|
68
|
+
attr_reader :attributes
|
69
|
+
|
70
|
+
def forced_changes
|
71
|
+
@forced_changes ||= {}
|
72
|
+
end
|
73
|
+
|
74
|
+
def attr_names
|
75
|
+
attributes.keys
|
76
|
+
end
|
75
77
|
|
76
|
-
|
78
|
+
def attribute_changed?(attr_name)
|
79
|
+
forced_changes.include?(attr_name) || !!attributes[attr_name].changed?
|
80
|
+
end
|
81
|
+
|
82
|
+
def fetch_value(attr_name)
|
83
|
+
attributes.fetch_value(attr_name)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class ForcedMutationTracker < AttributeMutationTracker # :nodoc:
|
88
|
+
def initialize(attributes)
|
89
|
+
super
|
90
|
+
@finalized_changes = nil
|
91
|
+
end
|
92
|
+
|
93
|
+
def changed_in_place?(attr_name)
|
94
|
+
false
|
95
|
+
end
|
96
|
+
|
97
|
+
def change_to_attribute(attr_name)
|
98
|
+
if finalized_changes&.include?(attr_name)
|
99
|
+
finalized_changes[attr_name].dup
|
100
|
+
else
|
101
|
+
super
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def forget_change(attr_name)
|
106
|
+
forced_changes.delete(attr_name)
|
107
|
+
end
|
108
|
+
|
109
|
+
def original_value(attr_name)
|
110
|
+
if changed?(attr_name)
|
111
|
+
forced_changes[attr_name]
|
112
|
+
else
|
113
|
+
fetch_value(attr_name)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def force_change(attr_name)
|
118
|
+
forced_changes[attr_name] = clone_value(attr_name) unless attribute_changed?(attr_name)
|
119
|
+
end
|
120
|
+
|
121
|
+
def finalize_changes
|
122
|
+
@finalized_changes = changes
|
123
|
+
end
|
77
124
|
|
78
125
|
private
|
126
|
+
attr_reader :finalized_changes
|
79
127
|
|
80
128
|
def attr_names
|
81
|
-
|
129
|
+
forced_changes.keys
|
130
|
+
end
|
131
|
+
|
132
|
+
def attribute_changed?(attr_name)
|
133
|
+
forced_changes.include?(attr_name)
|
134
|
+
end
|
135
|
+
|
136
|
+
def fetch_value(attr_name)
|
137
|
+
attributes.send(:_read_attribute, attr_name)
|
138
|
+
end
|
139
|
+
|
140
|
+
def clone_value(attr_name)
|
141
|
+
value = fetch_value(attr_name)
|
142
|
+
value.duplicable? ? value.clone : value
|
143
|
+
rescue TypeError, NoMethodError
|
144
|
+
value
|
82
145
|
end
|
83
146
|
end
|
84
147
|
|
85
148
|
class NullMutationTracker # :nodoc:
|
86
149
|
include Singleton
|
87
150
|
|
88
|
-
def changed_attribute_names
|
151
|
+
def changed_attribute_names
|
89
152
|
[]
|
90
153
|
end
|
91
154
|
|
92
|
-
def changed_values
|
155
|
+
def changed_values
|
93
156
|
{}
|
94
157
|
end
|
95
158
|
|
96
|
-
def changes
|
159
|
+
def changes
|
97
160
|
{}
|
98
161
|
end
|
99
162
|
|
100
163
|
def change_to_attribute(attr_name)
|
101
164
|
end
|
102
165
|
|
103
|
-
def any_changes?
|
166
|
+
def any_changes?
|
104
167
|
false
|
105
168
|
end
|
106
169
|
|
107
|
-
def changed?(
|
170
|
+
def changed?(attr_name, **)
|
108
171
|
false
|
109
172
|
end
|
110
173
|
|
111
|
-
def changed_in_place?(
|
174
|
+
def changed_in_place?(attr_name)
|
112
175
|
false
|
113
176
|
end
|
114
177
|
|
115
|
-
def
|
116
|
-
end
|
117
|
-
|
118
|
-
def original_value(*)
|
119
|
-
end
|
120
|
-
|
121
|
-
def force_change(*)
|
178
|
+
def original_value(attr_name)
|
122
179
|
end
|
123
180
|
end
|
124
181
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/core_ext/enumerable"
|
3
4
|
require "active_support/core_ext/object/deep_dup"
|
4
5
|
require "active_model/attribute_set/builder"
|
5
6
|
require "active_model/attribute_set/yaml_encoder"
|
@@ -13,11 +14,11 @@ module ActiveModel
|
|
13
14
|
end
|
14
15
|
|
15
16
|
def [](name)
|
16
|
-
attributes[name] ||
|
17
|
+
@attributes[name] || default_attribute(name)
|
17
18
|
end
|
18
19
|
|
19
20
|
def []=(name, value)
|
20
|
-
attributes[name] = value
|
21
|
+
@attributes[name] = value
|
21
22
|
end
|
22
23
|
|
23
24
|
def values_before_type_cast
|
@@ -25,9 +26,9 @@ module ActiveModel
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def to_hash
|
28
|
-
|
29
|
+
keys.index_with { |name| self[name].value }
|
29
30
|
end
|
30
|
-
|
31
|
+
alias :to_h :to_hash
|
31
32
|
|
32
33
|
def key?(name)
|
33
34
|
attributes.key?(name) && self[name].initialized?
|
@@ -37,48 +38,41 @@ module ActiveModel
|
|
37
38
|
attributes.each_key.select { |name| self[name].initialized? }
|
38
39
|
end
|
39
40
|
|
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
|
41
|
+
def fetch_value(name, &block)
|
42
|
+
self[name].value(&block)
|
50
43
|
end
|
51
44
|
|
52
45
|
def write_from_database(name, value)
|
53
|
-
attributes[name] = self[name].with_value_from_database(value)
|
46
|
+
@attributes[name] = self[name].with_value_from_database(value)
|
54
47
|
end
|
55
48
|
|
56
49
|
def write_from_user(name, value)
|
57
|
-
attributes
|
50
|
+
raise FrozenError, "can't modify frozen attributes" if frozen?
|
51
|
+
@attributes[name] = self[name].with_value_from_user(value)
|
52
|
+
value
|
58
53
|
end
|
59
54
|
|
60
55
|
def write_cast_value(name, value)
|
61
|
-
attributes[name] = self[name].with_cast_value(value)
|
56
|
+
@attributes[name] = self[name].with_cast_value(value)
|
57
|
+
value
|
62
58
|
end
|
63
59
|
|
64
60
|
def freeze
|
65
|
-
|
61
|
+
attributes.freeze
|
66
62
|
super
|
67
63
|
end
|
68
64
|
|
69
65
|
def deep_dup
|
70
|
-
|
71
|
-
copy.instance_variable_set(:@attributes, attributes.deep_dup)
|
72
|
-
end
|
66
|
+
AttributeSet.new(attributes.deep_dup)
|
73
67
|
end
|
74
68
|
|
75
69
|
def initialize_dup(_)
|
76
|
-
@attributes = attributes.dup
|
70
|
+
@attributes = @attributes.dup
|
77
71
|
super
|
78
72
|
end
|
79
73
|
|
80
74
|
def initialize_clone(_)
|
81
|
-
@attributes = attributes.clone
|
75
|
+
@attributes = @attributes.clone
|
82
76
|
super
|
83
77
|
end
|
84
78
|
|
@@ -89,7 +83,7 @@ module ActiveModel
|
|
89
83
|
end
|
90
84
|
|
91
85
|
def accessed
|
92
|
-
attributes.select { |
|
86
|
+
attributes.each_key.select { |name| self[name].has_been_read? }
|
93
87
|
end
|
94
88
|
|
95
89
|
def map(&block)
|
@@ -102,13 +96,11 @@ module ActiveModel
|
|
102
96
|
end
|
103
97
|
|
104
98
|
protected
|
105
|
-
|
106
99
|
attr_reader :attributes
|
107
100
|
|
108
101
|
private
|
109
|
-
|
110
|
-
|
111
|
-
attributes.select { |_, attr| attr.initialized? }
|
102
|
+
def default_attribute(name)
|
103
|
+
Attribute.null(name)
|
112
104
|
end
|
113
105
|
end
|
114
106
|
end
|
@@ -13,14 +13,86 @@ module ActiveModel
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def build_from_database(values = {}, additional_types = {})
|
16
|
-
|
17
|
-
AttributeSet.new(attributes)
|
16
|
+
LazyAttributeSet.new(values, types, additional_types, default_attributes)
|
18
17
|
end
|
19
18
|
end
|
20
19
|
end
|
21
20
|
|
21
|
+
class LazyAttributeSet < AttributeSet # :nodoc:
|
22
|
+
def initialize(values, types, additional_types, default_attributes, attributes = {})
|
23
|
+
super(attributes)
|
24
|
+
@values = values
|
25
|
+
@types = types
|
26
|
+
@additional_types = additional_types
|
27
|
+
@default_attributes = default_attributes
|
28
|
+
@casted_values = {}
|
29
|
+
@materialized = false
|
30
|
+
end
|
31
|
+
|
32
|
+
def key?(name)
|
33
|
+
(values.key?(name) || types.key?(name) || @attributes.key?(name)) && self[name].initialized?
|
34
|
+
end
|
35
|
+
|
36
|
+
def keys
|
37
|
+
keys = values.keys | types.keys | @attributes.keys
|
38
|
+
keys.keep_if { |name| self[name].initialized? }
|
39
|
+
end
|
40
|
+
|
41
|
+
def fetch_value(name, &block)
|
42
|
+
if attr = @attributes[name]
|
43
|
+
return attr.value(&block)
|
44
|
+
end
|
45
|
+
|
46
|
+
@casted_values.fetch(name) do
|
47
|
+
value_present = true
|
48
|
+
value = values.fetch(name) { value_present = false }
|
49
|
+
|
50
|
+
if value_present
|
51
|
+
type = additional_types.fetch(name, types[name])
|
52
|
+
@casted_values[name] = type.deserialize(value)
|
53
|
+
else
|
54
|
+
attr = default_attribute(name, value_present, value)
|
55
|
+
attr.value(&block)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
def attributes
|
62
|
+
unless @materialized
|
63
|
+
values.each_key { |key| self[key] }
|
64
|
+
types.each_key { |key| self[key] }
|
65
|
+
@materialized = true
|
66
|
+
end
|
67
|
+
@attributes
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
attr_reader :values, :types, :additional_types, :default_attributes
|
72
|
+
|
73
|
+
def default_attribute(
|
74
|
+
name,
|
75
|
+
value_present = true,
|
76
|
+
value = values.fetch(name) { value_present = false }
|
77
|
+
)
|
78
|
+
type = additional_types.fetch(name, types[name])
|
79
|
+
|
80
|
+
if value_present
|
81
|
+
@attributes[name] = Attribute.from_database(name, value, type, @casted_values[name])
|
82
|
+
elsif types.key?(name)
|
83
|
+
if attr = default_attributes[name]
|
84
|
+
@attributes[name] = attr.dup
|
85
|
+
else
|
86
|
+
@attributes[name] = Attribute.uninitialized(name, type)
|
87
|
+
end
|
88
|
+
else
|
89
|
+
Attribute.null(name)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
22
94
|
class LazyAttributeHash # :nodoc:
|
23
|
-
delegate :transform_values, :
|
95
|
+
delegate :transform_values, :each_value, :fetch, :except, to: :materialize
|
24
96
|
|
25
97
|
def initialize(types, values, additional_types, default_attributes, delegate_hash = {})
|
26
98
|
@types = types
|
@@ -40,9 +112,6 @@ module ActiveModel
|
|
40
112
|
end
|
41
113
|
|
42
114
|
def []=(key, value)
|
43
|
-
if frozen?
|
44
|
-
raise RuntimeError, "Can't modify frozen hash"
|
45
|
-
end
|
46
115
|
delegate_hash[key] = value
|
47
116
|
end
|
48
117
|
|
@@ -57,14 +126,9 @@ module ActiveModel
|
|
57
126
|
super
|
58
127
|
end
|
59
128
|
|
60
|
-
def
|
129
|
+
def each_key(&block)
|
61
130
|
keys = types.keys | values.keys | delegate_hash.keys
|
62
|
-
keys.
|
63
|
-
attribute = self[key]
|
64
|
-
if yield(key, attribute)
|
65
|
-
hash[key] = attribute
|
66
|
-
end
|
67
|
-
end
|
131
|
+
keys.each(&block)
|
68
132
|
end
|
69
133
|
|
70
134
|
def ==(other)
|
@@ -81,6 +145,9 @@ module ActiveModel
|
|
81
145
|
|
82
146
|
def marshal_load(values)
|
83
147
|
if values.is_a?(Hash)
|
148
|
+
ActiveSupport::Deprecation.warn(<<~MSG)
|
149
|
+
Marshalling load from legacy attributes format is deprecated and will be removed in Rails 6.2.
|
150
|
+
MSG
|
84
151
|
empty_hash = {}.freeze
|
85
152
|
initialize(empty_hash, empty_hash, empty_hash, empty_hash, values)
|
86
153
|
@materialized = true
|
@@ -90,9 +157,6 @@ module ActiveModel
|
|
90
157
|
end
|
91
158
|
|
92
159
|
protected
|
93
|
-
|
94
|
-
attr_reader :types, :values, :additional_types, :delegate_hash, :default_attributes
|
95
|
-
|
96
160
|
def materialize
|
97
161
|
unless @materialized
|
98
162
|
values.each_key { |key| self[key] }
|
@@ -105,6 +169,7 @@ module ActiveModel
|
|
105
169
|
end
|
106
170
|
|
107
171
|
private
|
172
|
+
attr_reader :types, :values, :additional_types, :delegate_hash, :default_attributes
|
108
173
|
|
109
174
|
def assign_default_value(name)
|
110
175
|
type = additional_types.fetch(name, types[name])
|