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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +172 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +266 -0
- data/lib/active_model.rb +77 -0
- data/lib/active_model/attribute.rb +247 -0
- data/lib/active_model/attribute/user_provided_default.rb +51 -0
- data/lib/active_model/attribute_assignment.rb +57 -0
- data/lib/active_model/attribute_methods.rb +517 -0
- data/lib/active_model/attribute_mutation_tracker.rb +178 -0
- data/lib/active_model/attribute_set.rb +106 -0
- data/lib/active_model/attribute_set/builder.rb +124 -0
- data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
- data/lib/active_model/attributes.rb +138 -0
- data/lib/active_model/callbacks.rb +156 -0
- data/lib/active_model/conversion.rb +111 -0
- data/lib/active_model/dirty.rb +280 -0
- data/lib/active_model/errors.rb +601 -0
- data/lib/active_model/forbidden_attributes_protection.rb +31 -0
- data/lib/active_model/gem_version.rb +17 -0
- data/lib/active_model/lint.rb +118 -0
- data/lib/active_model/locale/en.yml +36 -0
- data/lib/active_model/model.rb +99 -0
- data/lib/active_model/naming.rb +334 -0
- data/lib/active_model/railtie.rb +20 -0
- data/lib/active_model/secure_password.rb +128 -0
- data/lib/active_model/serialization.rb +192 -0
- data/lib/active_model/serializers/json.rb +147 -0
- data/lib/active_model/translation.rb +70 -0
- data/lib/active_model/type.rb +53 -0
- data/lib/active_model/type/big_integer.rb +15 -0
- data/lib/active_model/type/binary.rb +52 -0
- data/lib/active_model/type/boolean.rb +47 -0
- data/lib/active_model/type/date.rb +53 -0
- data/lib/active_model/type/date_time.rb +47 -0
- data/lib/active_model/type/decimal.rb +70 -0
- data/lib/active_model/type/float.rb +34 -0
- data/lib/active_model/type/helpers.rb +7 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +45 -0
- data/lib/active_model/type/helpers/mutable.rb +20 -0
- data/lib/active_model/type/helpers/numeric.rb +44 -0
- data/lib/active_model/type/helpers/time_value.rb +81 -0
- data/lib/active_model/type/helpers/timezone.rb +19 -0
- data/lib/active_model/type/immutable_string.rb +32 -0
- data/lib/active_model/type/integer.rb +58 -0
- data/lib/active_model/type/registry.rb +62 -0
- data/lib/active_model/type/string.rb +26 -0
- data/lib/active_model/type/time.rb +47 -0
- data/lib/active_model/type/value.rb +126 -0
- data/lib/active_model/validations.rb +437 -0
- data/lib/active_model/validations/absence.rb +33 -0
- data/lib/active_model/validations/acceptance.rb +102 -0
- data/lib/active_model/validations/callbacks.rb +122 -0
- data/lib/active_model/validations/clusivity.rb +54 -0
- data/lib/active_model/validations/confirmation.rb +80 -0
- data/lib/active_model/validations/exclusion.rb +49 -0
- data/lib/active_model/validations/format.rb +114 -0
- data/lib/active_model/validations/helper_methods.rb +15 -0
- data/lib/active_model/validations/inclusion.rb +47 -0
- data/lib/active_model/validations/length.rb +129 -0
- data/lib/active_model/validations/numericality.rb +189 -0
- data/lib/active_model/validations/presence.rb +39 -0
- data/lib/active_model/validations/validates.rb +174 -0
- data/lib/active_model/validations/with.rb +147 -0
- data/lib/active_model/validator.rb +183 -0
- data/lib/active_model/version.rb +10 -0
- metadata +125 -0
@@ -0,0 +1,178 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/hash/indifferent_access"
|
4
|
+
require "active_support/core_ext/object/duplicable"
|
5
|
+
|
6
|
+
module ActiveModel
|
7
|
+
class AttributeMutationTracker # :nodoc:
|
8
|
+
OPTION_NOT_GIVEN = Object.new
|
9
|
+
|
10
|
+
def initialize(attributes, forced_changes = Set.new)
|
11
|
+
@attributes = attributes
|
12
|
+
@forced_changes = forced_changes
|
13
|
+
end
|
14
|
+
|
15
|
+
def changed_attribute_names
|
16
|
+
attr_names.select { |attr_name| changed?(attr_name) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def changed_values
|
20
|
+
attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
|
21
|
+
if changed?(attr_name)
|
22
|
+
result[attr_name] = original_value(attr_name)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def changes
|
28
|
+
attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
|
29
|
+
if change = change_to_attribute(attr_name)
|
30
|
+
result.merge!(attr_name => change)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def change_to_attribute(attr_name)
|
36
|
+
if changed?(attr_name)
|
37
|
+
[original_value(attr_name), fetch_value(attr_name)]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def any_changes?
|
42
|
+
attr_names.any? { |attr| changed?(attr) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN)
|
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)
|
49
|
+
end
|
50
|
+
|
51
|
+
def changed_in_place?(attr_name)
|
52
|
+
attributes[attr_name].changed_in_place?
|
53
|
+
end
|
54
|
+
|
55
|
+
def forget_change(attr_name)
|
56
|
+
attributes[attr_name] = attributes[attr_name].forgetting_assignment
|
57
|
+
forced_changes.delete(attr_name)
|
58
|
+
end
|
59
|
+
|
60
|
+
def original_value(attr_name)
|
61
|
+
attributes[attr_name].original_value
|
62
|
+
end
|
63
|
+
|
64
|
+
def force_change(attr_name)
|
65
|
+
forced_changes << attr_name
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
attr_reader :attributes, :forced_changes
|
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
|
+
|
122
|
+
private
|
123
|
+
attr_reader :finalized_changes
|
124
|
+
|
125
|
+
def attr_names
|
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
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
class NullMutationTracker # :nodoc:
|
146
|
+
include Singleton
|
147
|
+
|
148
|
+
def changed_attribute_names
|
149
|
+
[]
|
150
|
+
end
|
151
|
+
|
152
|
+
def changed_values
|
153
|
+
{}
|
154
|
+
end
|
155
|
+
|
156
|
+
def changes
|
157
|
+
{}
|
158
|
+
end
|
159
|
+
|
160
|
+
def change_to_attribute(attr_name)
|
161
|
+
end
|
162
|
+
|
163
|
+
def any_changes?
|
164
|
+
false
|
165
|
+
end
|
166
|
+
|
167
|
+
def changed?(attr_name, **)
|
168
|
+
false
|
169
|
+
end
|
170
|
+
|
171
|
+
def changed_in_place?(attr_name)
|
172
|
+
false
|
173
|
+
end
|
174
|
+
|
175
|
+
def original_value(attr_name)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/object/deep_dup"
|
4
|
+
require "active_model/attribute_set/builder"
|
5
|
+
require "active_model/attribute_set/yaml_encoder"
|
6
|
+
|
7
|
+
module ActiveModel
|
8
|
+
class AttributeSet # :nodoc:
|
9
|
+
delegate :each_value, :fetch, :except, to: :attributes
|
10
|
+
|
11
|
+
def initialize(attributes)
|
12
|
+
@attributes = attributes
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](name)
|
16
|
+
attributes[name] || Attribute.null(name)
|
17
|
+
end
|
18
|
+
|
19
|
+
def []=(name, value)
|
20
|
+
attributes[name] = value
|
21
|
+
end
|
22
|
+
|
23
|
+
def values_before_type_cast
|
24
|
+
attributes.transform_values(&:value_before_type_cast)
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_hash
|
28
|
+
initialized_attributes.transform_values(&:value)
|
29
|
+
end
|
30
|
+
alias_method :to_h, :to_hash
|
31
|
+
|
32
|
+
def key?(name)
|
33
|
+
attributes.key?(name) && self[name].initialized?
|
34
|
+
end
|
35
|
+
|
36
|
+
def keys
|
37
|
+
attributes.each_key.select { |name| self[name].initialized? }
|
38
|
+
end
|
39
|
+
|
40
|
+
def fetch_value(name, &block)
|
41
|
+
self[name].value(&block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def write_from_database(name, value)
|
45
|
+
attributes[name] = self[name].with_value_from_database(value)
|
46
|
+
end
|
47
|
+
|
48
|
+
def write_from_user(name, value)
|
49
|
+
attributes[name] = self[name].with_value_from_user(value)
|
50
|
+
end
|
51
|
+
|
52
|
+
def write_cast_value(name, value)
|
53
|
+
attributes[name] = self[name].with_cast_value(value)
|
54
|
+
end
|
55
|
+
|
56
|
+
def freeze
|
57
|
+
@attributes.freeze
|
58
|
+
super
|
59
|
+
end
|
60
|
+
|
61
|
+
def deep_dup
|
62
|
+
self.class.allocate.tap do |copy|
|
63
|
+
copy.instance_variable_set(:@attributes, attributes.deep_dup)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def initialize_dup(_)
|
68
|
+
@attributes = attributes.dup
|
69
|
+
super
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize_clone(_)
|
73
|
+
@attributes = attributes.clone
|
74
|
+
super
|
75
|
+
end
|
76
|
+
|
77
|
+
def reset(key)
|
78
|
+
if key?(key)
|
79
|
+
write_from_database(key, nil)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def accessed
|
84
|
+
attributes.select { |_, attr| attr.has_been_read? }.keys
|
85
|
+
end
|
86
|
+
|
87
|
+
def map(&block)
|
88
|
+
new_attributes = attributes.transform_values(&block)
|
89
|
+
AttributeSet.new(new_attributes)
|
90
|
+
end
|
91
|
+
|
92
|
+
def ==(other)
|
93
|
+
attributes == other.attributes
|
94
|
+
end
|
95
|
+
|
96
|
+
protected
|
97
|
+
|
98
|
+
attr_reader :attributes
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def initialized_attributes
|
103
|
+
attributes.select { |_, attr| attr.initialized? }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/attribute"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
class AttributeSet # :nodoc:
|
7
|
+
class Builder # :nodoc:
|
8
|
+
attr_reader :types, :default_attributes
|
9
|
+
|
10
|
+
def initialize(types, default_attributes = {})
|
11
|
+
@types = types
|
12
|
+
@default_attributes = default_attributes
|
13
|
+
end
|
14
|
+
|
15
|
+
def build_from_database(values = {}, additional_types = {})
|
16
|
+
attributes = LazyAttributeHash.new(types, values, additional_types, default_attributes)
|
17
|
+
AttributeSet.new(attributes)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class LazyAttributeHash # :nodoc:
|
23
|
+
delegate :transform_values, :each_key, :each_value, :fetch, :except, to: :materialize
|
24
|
+
|
25
|
+
def initialize(types, values, additional_types, default_attributes, delegate_hash = {})
|
26
|
+
@types = types
|
27
|
+
@values = values
|
28
|
+
@additional_types = additional_types
|
29
|
+
@materialized = false
|
30
|
+
@delegate_hash = delegate_hash
|
31
|
+
@default_attributes = default_attributes
|
32
|
+
end
|
33
|
+
|
34
|
+
def key?(key)
|
35
|
+
delegate_hash.key?(key) || values.key?(key) || types.key?(key)
|
36
|
+
end
|
37
|
+
|
38
|
+
def [](key)
|
39
|
+
delegate_hash[key] || assign_default_value(key)
|
40
|
+
end
|
41
|
+
|
42
|
+
def []=(key, value)
|
43
|
+
if frozen?
|
44
|
+
raise RuntimeError, "Can't modify frozen hash"
|
45
|
+
end
|
46
|
+
delegate_hash[key] = value
|
47
|
+
end
|
48
|
+
|
49
|
+
def deep_dup
|
50
|
+
dup.tap do |copy|
|
51
|
+
copy.instance_variable_set(:@delegate_hash, delegate_hash.transform_values(&:dup))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def initialize_dup(_)
|
56
|
+
@delegate_hash = Hash[delegate_hash]
|
57
|
+
super
|
58
|
+
end
|
59
|
+
|
60
|
+
def select
|
61
|
+
keys = types.keys | values.keys | delegate_hash.keys
|
62
|
+
keys.each_with_object({}) do |key, hash|
|
63
|
+
attribute = self[key]
|
64
|
+
if yield(key, attribute)
|
65
|
+
hash[key] = attribute
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def ==(other)
|
71
|
+
if other.is_a?(LazyAttributeHash)
|
72
|
+
materialize == other.materialize
|
73
|
+
else
|
74
|
+
materialize == other
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def marshal_dump
|
79
|
+
[@types, @values, @additional_types, @default_attributes, @delegate_hash]
|
80
|
+
end
|
81
|
+
|
82
|
+
def marshal_load(values)
|
83
|
+
if values.is_a?(Hash)
|
84
|
+
empty_hash = {}.freeze
|
85
|
+
initialize(empty_hash, empty_hash, empty_hash, empty_hash, values)
|
86
|
+
@materialized = true
|
87
|
+
else
|
88
|
+
initialize(*values)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
protected
|
93
|
+
def materialize
|
94
|
+
unless @materialized
|
95
|
+
values.each_key { |key| self[key] }
|
96
|
+
types.each_key { |key| self[key] }
|
97
|
+
unless frozen?
|
98
|
+
@materialized = true
|
99
|
+
end
|
100
|
+
end
|
101
|
+
delegate_hash
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
attr_reader :types, :values, :additional_types, :delegate_hash, :default_attributes
|
106
|
+
|
107
|
+
def assign_default_value(name)
|
108
|
+
type = additional_types.fetch(name, types[name])
|
109
|
+
value_present = true
|
110
|
+
value = values.fetch(name) { value_present = false }
|
111
|
+
|
112
|
+
if value_present
|
113
|
+
delegate_hash[name] = Attribute.from_database(name, value, type)
|
114
|
+
elsif types.key?(name)
|
115
|
+
attr = default_attributes[name]
|
116
|
+
if attr
|
117
|
+
delegate_hash[name] = attr.dup
|
118
|
+
else
|
119
|
+
delegate_hash[name] = Attribute.uninitialized(name, type)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
class AttributeSet
|
5
|
+
# Attempts to do more intelligent YAML dumping of an
|
6
|
+
# ActiveModel::AttributeSet to reduce the size of the resulting string
|
7
|
+
class YAMLEncoder # :nodoc:
|
8
|
+
def initialize(default_types)
|
9
|
+
@default_types = default_types
|
10
|
+
end
|
11
|
+
|
12
|
+
def encode(attribute_set, coder)
|
13
|
+
coder["concise_attributes"] = attribute_set.each_value.map do |attr|
|
14
|
+
if attr.type.equal?(default_types[attr.name])
|
15
|
+
attr.with_type(nil)
|
16
|
+
else
|
17
|
+
attr
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def decode(coder)
|
23
|
+
if coder["attributes"]
|
24
|
+
coder["attributes"]
|
25
|
+
else
|
26
|
+
attributes_hash = Hash[coder["concise_attributes"].map do |attr|
|
27
|
+
if attr.type.nil?
|
28
|
+
attr = attr.with_type(default_types[attr.name])
|
29
|
+
end
|
30
|
+
[attr.name, attr]
|
31
|
+
end]
|
32
|
+
AttributeSet.new(attributes_hash)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
attr_reader :default_types
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|