omg-activemodel 8.0.0.alpha1
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 +67 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +266 -0
- data/lib/active_model/access.rb +16 -0
- data/lib/active_model/api.rb +99 -0
- data/lib/active_model/attribute/user_provided_default.rb +55 -0
- data/lib/active_model/attribute.rb +277 -0
- data/lib/active_model/attribute_assignment.rb +78 -0
- data/lib/active_model/attribute_methods.rb +592 -0
- data/lib/active_model/attribute_mutation_tracker.rb +189 -0
- data/lib/active_model/attribute_registration.rb +117 -0
- data/lib/active_model/attribute_set/builder.rb +182 -0
- data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
- data/lib/active_model/attribute_set.rb +118 -0
- data/lib/active_model/attributes.rb +165 -0
- data/lib/active_model/callbacks.rb +155 -0
- data/lib/active_model/conversion.rb +121 -0
- data/lib/active_model/deprecator.rb +7 -0
- data/lib/active_model/dirty.rb +416 -0
- data/lib/active_model/error.rb +208 -0
- data/lib/active_model/errors.rb +547 -0
- data/lib/active_model/forbidden_attributes_protection.rb +33 -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 +38 -0
- data/lib/active_model/model.rb +78 -0
- data/lib/active_model/naming.rb +359 -0
- data/lib/active_model/nested_error.rb +22 -0
- data/lib/active_model/railtie.rb +24 -0
- data/lib/active_model/secure_password.rb +231 -0
- data/lib/active_model/serialization.rb +198 -0
- data/lib/active_model/serializers/json.rb +154 -0
- data/lib/active_model/translation.rb +78 -0
- data/lib/active_model/type/big_integer.rb +36 -0
- data/lib/active_model/type/binary.rb +62 -0
- data/lib/active_model/type/boolean.rb +48 -0
- data/lib/active_model/type/date.rb +78 -0
- data/lib/active_model/type/date_time.rb +88 -0
- data/lib/active_model/type/decimal.rb +107 -0
- data/lib/active_model/type/float.rb +64 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +53 -0
- data/lib/active_model/type/helpers/mutable.rb +24 -0
- data/lib/active_model/type/helpers/numeric.rb +61 -0
- data/lib/active_model/type/helpers/time_value.rb +127 -0
- data/lib/active_model/type/helpers/timezone.rb +23 -0
- data/lib/active_model/type/helpers.rb +7 -0
- data/lib/active_model/type/immutable_string.rb +71 -0
- data/lib/active_model/type/integer.rb +113 -0
- data/lib/active_model/type/registry.rb +37 -0
- data/lib/active_model/type/serialize_cast_value.rb +47 -0
- data/lib/active_model/type/string.rb +43 -0
- data/lib/active_model/type/time.rb +87 -0
- data/lib/active_model/type/value.rb +157 -0
- data/lib/active_model/type.rb +55 -0
- data/lib/active_model/validations/absence.rb +33 -0
- data/lib/active_model/validations/acceptance.rb +113 -0
- data/lib/active_model/validations/callbacks.rb +119 -0
- data/lib/active_model/validations/clusivity.rb +54 -0
- data/lib/active_model/validations/comparability.rb +18 -0
- data/lib/active_model/validations/comparison.rb +90 -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 +112 -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 +130 -0
- data/lib/active_model/validations/numericality.rb +222 -0
- data/lib/active_model/validations/presence.rb +39 -0
- data/lib/active_model/validations/resolve_value.rb +26 -0
- data/lib/active_model/validations/validates.rb +175 -0
- data/lib/active_model/validations/with.rb +154 -0
- data/lib/active_model/validations.rb +489 -0
- data/lib/active_model/validator.rb +190 -0
- data/lib/active_model/version.rb +10 -0
- data/lib/active_model.rb +84 -0
- metadata +139 -0
@@ -0,0 +1,189 @@
|
|
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)
|
11
|
+
@attributes = attributes
|
12
|
+
end
|
13
|
+
|
14
|
+
def changed_attribute_names
|
15
|
+
attr_names.select { |attr_name| changed?(attr_name) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def changed_values
|
19
|
+
attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
|
20
|
+
if changed?(attr_name)
|
21
|
+
result[attr_name] = original_value(attr_name)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def changes
|
27
|
+
attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
|
28
|
+
if change = change_to_attribute(attr_name)
|
29
|
+
result.merge!(attr_name => change)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def change_to_attribute(attr_name)
|
35
|
+
if changed?(attr_name)
|
36
|
+
[original_value(attr_name), fetch_value(attr_name)]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def any_changes?
|
41
|
+
attr_names.any? { |attr| changed?(attr) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN)
|
45
|
+
attribute_changed?(attr_name) &&
|
46
|
+
(OPTION_NOT_GIVEN == from || original_value(attr_name) == type_cast(attr_name, from)) &&
|
47
|
+
(OPTION_NOT_GIVEN == to || fetch_value(attr_name) == type_cast(attr_name, to))
|
48
|
+
end
|
49
|
+
|
50
|
+
def changed_in_place?(attr_name)
|
51
|
+
attributes[attr_name].changed_in_place?
|
52
|
+
end
|
53
|
+
|
54
|
+
def forget_change(attr_name)
|
55
|
+
attributes[attr_name] = attributes[attr_name].forgetting_assignment
|
56
|
+
forced_changes.delete(attr_name)
|
57
|
+
end
|
58
|
+
|
59
|
+
def original_value(attr_name)
|
60
|
+
attributes[attr_name].original_value
|
61
|
+
end
|
62
|
+
|
63
|
+
def force_change(attr_name)
|
64
|
+
forced_changes[attr_name] = fetch_value(attr_name)
|
65
|
+
end
|
66
|
+
|
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
|
77
|
+
|
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
|
+
|
86
|
+
def type_cast(attr_name, value)
|
87
|
+
attributes[attr_name].type_cast(value)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class ForcedMutationTracker < AttributeMutationTracker # :nodoc:
|
92
|
+
def initialize(attributes)
|
93
|
+
super
|
94
|
+
@finalized_changes = nil
|
95
|
+
end
|
96
|
+
|
97
|
+
def changed_in_place?(attr_name)
|
98
|
+
false
|
99
|
+
end
|
100
|
+
|
101
|
+
def change_to_attribute(attr_name)
|
102
|
+
if finalized_changes&.include?(attr_name)
|
103
|
+
finalized_changes[attr_name].dup
|
104
|
+
else
|
105
|
+
super
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def forget_change(attr_name)
|
110
|
+
forced_changes.delete(attr_name)
|
111
|
+
end
|
112
|
+
|
113
|
+
def original_value(attr_name)
|
114
|
+
if changed?(attr_name)
|
115
|
+
forced_changes[attr_name]
|
116
|
+
else
|
117
|
+
fetch_value(attr_name)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def force_change(attr_name)
|
122
|
+
forced_changes[attr_name] = clone_value(attr_name) unless attribute_changed?(attr_name)
|
123
|
+
end
|
124
|
+
|
125
|
+
def finalize_changes
|
126
|
+
@finalized_changes = changes
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
attr_reader :finalized_changes
|
131
|
+
|
132
|
+
def attr_names
|
133
|
+
forced_changes.keys
|
134
|
+
end
|
135
|
+
|
136
|
+
def attribute_changed?(attr_name)
|
137
|
+
forced_changes.include?(attr_name)
|
138
|
+
end
|
139
|
+
|
140
|
+
def fetch_value(attr_name)
|
141
|
+
attributes.send(:_read_attribute, attr_name)
|
142
|
+
end
|
143
|
+
|
144
|
+
def clone_value(attr_name)
|
145
|
+
value = fetch_value(attr_name)
|
146
|
+
value.duplicable? ? value.clone : value
|
147
|
+
rescue TypeError, NoMethodError
|
148
|
+
value
|
149
|
+
end
|
150
|
+
|
151
|
+
def type_cast(attr_name, value)
|
152
|
+
value
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
class NullMutationTracker # :nodoc:
|
157
|
+
include Singleton
|
158
|
+
|
159
|
+
def changed_attribute_names
|
160
|
+
[]
|
161
|
+
end
|
162
|
+
|
163
|
+
def changed_values
|
164
|
+
{}
|
165
|
+
end
|
166
|
+
|
167
|
+
def changes
|
168
|
+
{}
|
169
|
+
end
|
170
|
+
|
171
|
+
def change_to_attribute(attr_name)
|
172
|
+
end
|
173
|
+
|
174
|
+
def any_changes?
|
175
|
+
false
|
176
|
+
end
|
177
|
+
|
178
|
+
def changed?(attr_name, **)
|
179
|
+
false
|
180
|
+
end
|
181
|
+
|
182
|
+
def changed_in_place?(attr_name)
|
183
|
+
false
|
184
|
+
end
|
185
|
+
|
186
|
+
def original_value(attr_name)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/class/subclasses"
|
4
|
+
require "active_model/attribute_set"
|
5
|
+
require "active_model/attribute/user_provided_default"
|
6
|
+
|
7
|
+
module ActiveModel
|
8
|
+
module AttributeRegistration # :nodoc:
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
module ClassMethods # :nodoc:
|
12
|
+
def attribute(name, type = nil, default: (no_default = true), **options)
|
13
|
+
name = resolve_attribute_name(name)
|
14
|
+
type = resolve_type_name(type, **options) if type.is_a?(Symbol)
|
15
|
+
type = hook_attribute_type(name, type) if type
|
16
|
+
|
17
|
+
pending_attribute_modifications << PendingType.new(name, type) if type || no_default
|
18
|
+
pending_attribute_modifications << PendingDefault.new(name, default) unless no_default
|
19
|
+
|
20
|
+
reset_default_attributes
|
21
|
+
end
|
22
|
+
|
23
|
+
def decorate_attributes(names = nil, &decorator) # :nodoc:
|
24
|
+
names = names&.map { |name| resolve_attribute_name(name) }
|
25
|
+
|
26
|
+
pending_attribute_modifications << PendingDecorator.new(names, decorator)
|
27
|
+
|
28
|
+
reset_default_attributes
|
29
|
+
end
|
30
|
+
|
31
|
+
def _default_attributes # :nodoc:
|
32
|
+
@default_attributes ||= AttributeSet.new({}).tap do |attribute_set|
|
33
|
+
apply_pending_attribute_modifications(attribute_set)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def attribute_types # :nodoc:
|
38
|
+
@attribute_types ||= _default_attributes.cast_types.tap do |hash|
|
39
|
+
hash.default = Type.default_value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def type_for_attribute(attribute_name, &block)
|
44
|
+
attribute_name = resolve_attribute_name(attribute_name)
|
45
|
+
|
46
|
+
if block
|
47
|
+
attribute_types.fetch(attribute_name, &block)
|
48
|
+
else
|
49
|
+
attribute_types[attribute_name]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
PendingType = Struct.new(:name, :type) do # :nodoc:
|
55
|
+
def apply_to(attribute_set)
|
56
|
+
attribute = attribute_set[name]
|
57
|
+
attribute_set[name] = attribute.with_type(type || attribute.type)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
PendingDefault = Struct.new(:name, :default) do # :nodoc:
|
62
|
+
def apply_to(attribute_set)
|
63
|
+
attribute_set[name] = attribute_set[name].with_user_default(default)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
PendingDecorator = Struct.new(:names, :decorator) do # :nodoc:
|
68
|
+
def apply_to(attribute_set)
|
69
|
+
(names || attribute_set.keys).each do |name|
|
70
|
+
attribute = attribute_set[name]
|
71
|
+
type = decorator.call(name, attribute.type)
|
72
|
+
attribute_set[name] = attribute.with_type(type) if type
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def pending_attribute_modifications
|
78
|
+
@pending_attribute_modifications ||= []
|
79
|
+
end
|
80
|
+
|
81
|
+
def apply_pending_attribute_modifications(attribute_set)
|
82
|
+
if superclass.respond_to?(:apply_pending_attribute_modifications, true)
|
83
|
+
superclass.send(:apply_pending_attribute_modifications, attribute_set)
|
84
|
+
end
|
85
|
+
|
86
|
+
pending_attribute_modifications.each do |modification|
|
87
|
+
modification.apply_to(attribute_set)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def reset_default_attributes
|
92
|
+
reset_default_attributes!
|
93
|
+
subclasses.each { |subclass| subclass.send(:reset_default_attributes) }
|
94
|
+
end
|
95
|
+
|
96
|
+
def reset_default_attributes!
|
97
|
+
@default_attributes = nil
|
98
|
+
@attribute_types = nil
|
99
|
+
end
|
100
|
+
|
101
|
+
def resolve_attribute_name(name)
|
102
|
+
name.to_s
|
103
|
+
end
|
104
|
+
|
105
|
+
def resolve_type_name(name, **options)
|
106
|
+
Type.lookup(name, **options)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Hook for other modules to override. The attribute type is passed
|
110
|
+
# through this method immediately after it is resolved, before any type
|
111
|
+
# decorations are applied.
|
112
|
+
def hook_attribute_type(attribute, type)
|
113
|
+
type
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,182 @@
|
|
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
|
+
LazyAttributeSet.new(values, types, additional_types, default_attributes)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
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
|
+
|
94
|
+
class LazyAttributeHash # :nodoc:
|
95
|
+
delegate :transform_values, :each_value, :fetch, :except, to: :materialize
|
96
|
+
|
97
|
+
def initialize(types, values, additional_types, default_attributes, delegate_hash = {})
|
98
|
+
@types = types
|
99
|
+
@values = values
|
100
|
+
@additional_types = additional_types
|
101
|
+
@materialized = false
|
102
|
+
@delegate_hash = delegate_hash
|
103
|
+
@default_attributes = default_attributes
|
104
|
+
end
|
105
|
+
|
106
|
+
def key?(key)
|
107
|
+
delegate_hash.key?(key) || values.key?(key) || types.key?(key)
|
108
|
+
end
|
109
|
+
|
110
|
+
def [](key)
|
111
|
+
delegate_hash[key] || assign_default_value(key)
|
112
|
+
end
|
113
|
+
|
114
|
+
def []=(key, value)
|
115
|
+
delegate_hash[key] = value
|
116
|
+
end
|
117
|
+
|
118
|
+
def deep_dup
|
119
|
+
dup.tap do |copy|
|
120
|
+
copy.instance_variable_set(:@delegate_hash, delegate_hash.transform_values(&:dup))
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def initialize_dup(_)
|
125
|
+
@delegate_hash = Hash[delegate_hash]
|
126
|
+
super
|
127
|
+
end
|
128
|
+
|
129
|
+
def each_key(&block)
|
130
|
+
keys = types.keys | values.keys | delegate_hash.keys
|
131
|
+
keys.each(&block)
|
132
|
+
end
|
133
|
+
|
134
|
+
def ==(other)
|
135
|
+
if other.is_a?(LazyAttributeHash)
|
136
|
+
materialize == other.materialize
|
137
|
+
else
|
138
|
+
materialize == other
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def marshal_dump
|
143
|
+
[@types, @values, @additional_types, @default_attributes, @delegate_hash]
|
144
|
+
end
|
145
|
+
|
146
|
+
def marshal_load(values)
|
147
|
+
initialize(*values)
|
148
|
+
end
|
149
|
+
|
150
|
+
protected
|
151
|
+
def materialize
|
152
|
+
unless @materialized
|
153
|
+
values.each_key { |key| self[key] }
|
154
|
+
types.each_key { |key| self[key] }
|
155
|
+
unless frozen?
|
156
|
+
@materialized = true
|
157
|
+
end
|
158
|
+
end
|
159
|
+
delegate_hash
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
attr_reader :types, :values, :additional_types, :delegate_hash, :default_attributes
|
164
|
+
|
165
|
+
def assign_default_value(name)
|
166
|
+
type = additional_types.fetch(name, types[name])
|
167
|
+
value_present = true
|
168
|
+
value = values.fetch(name) { value_present = false }
|
169
|
+
|
170
|
+
if value_present
|
171
|
+
delegate_hash[name] = Attribute.from_database(name, value, type)
|
172
|
+
elsif types.key?(name)
|
173
|
+
attr = default_attributes[name]
|
174
|
+
if attr
|
175
|
+
delegate_hash[name] = attr.dup
|
176
|
+
else
|
177
|
+
delegate_hash[name] = Attribute.uninitialized(name, type)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
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
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/enumerable"
|
4
|
+
require "active_support/core_ext/object/deep_dup"
|
5
|
+
require "active_model/attribute_set/builder"
|
6
|
+
require "active_model/attribute_set/yaml_encoder"
|
7
|
+
|
8
|
+
module ActiveModel
|
9
|
+
class AttributeSet # :nodoc:
|
10
|
+
delegate :each_value, :fetch, :except, to: :attributes
|
11
|
+
|
12
|
+
def initialize(attributes)
|
13
|
+
@attributes = attributes
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](name)
|
17
|
+
@attributes[name] || default_attribute(name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def []=(name, value)
|
21
|
+
@attributes[name] = value
|
22
|
+
end
|
23
|
+
|
24
|
+
def cast_types
|
25
|
+
attributes.transform_values(&:type)
|
26
|
+
end
|
27
|
+
|
28
|
+
def values_before_type_cast
|
29
|
+
attributes.transform_values(&:value_before_type_cast)
|
30
|
+
end
|
31
|
+
|
32
|
+
def values_for_database
|
33
|
+
attributes.transform_values(&:value_for_database)
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_hash
|
37
|
+
keys.index_with { |name| self[name].value }
|
38
|
+
end
|
39
|
+
alias :to_h :to_hash
|
40
|
+
|
41
|
+
def key?(name)
|
42
|
+
attributes.key?(name) && self[name].initialized?
|
43
|
+
end
|
44
|
+
alias :include? :key?
|
45
|
+
|
46
|
+
def keys
|
47
|
+
attributes.each_key.select { |name| self[name].initialized? }
|
48
|
+
end
|
49
|
+
|
50
|
+
def fetch_value(name, &block)
|
51
|
+
self[name].value(&block)
|
52
|
+
end
|
53
|
+
|
54
|
+
def write_from_database(name, value)
|
55
|
+
@attributes[name] = self[name].with_value_from_database(value)
|
56
|
+
end
|
57
|
+
|
58
|
+
def write_from_user(name, value)
|
59
|
+
raise FrozenError, "can't modify frozen attributes" if frozen?
|
60
|
+
@attributes[name] = self[name].with_value_from_user(value)
|
61
|
+
value
|
62
|
+
end
|
63
|
+
|
64
|
+
def write_cast_value(name, value)
|
65
|
+
@attributes[name] = self[name].with_cast_value(value)
|
66
|
+
end
|
67
|
+
|
68
|
+
def freeze
|
69
|
+
attributes.freeze
|
70
|
+
super
|
71
|
+
end
|
72
|
+
|
73
|
+
def deep_dup
|
74
|
+
AttributeSet.new(attributes.deep_dup)
|
75
|
+
end
|
76
|
+
|
77
|
+
def initialize_dup(_)
|
78
|
+
@attributes = @attributes.dup
|
79
|
+
super
|
80
|
+
end
|
81
|
+
|
82
|
+
def initialize_clone(_)
|
83
|
+
@attributes = @attributes.clone
|
84
|
+
super
|
85
|
+
end
|
86
|
+
|
87
|
+
def reset(key)
|
88
|
+
if key?(key)
|
89
|
+
write_from_database(key, nil)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def accessed
|
94
|
+
attributes.each_key.select { |name| self[name].has_been_read? }
|
95
|
+
end
|
96
|
+
|
97
|
+
def map(&block)
|
98
|
+
new_attributes = attributes.transform_values(&block)
|
99
|
+
AttributeSet.new(new_attributes)
|
100
|
+
end
|
101
|
+
|
102
|
+
def reverse_merge!(target_attributes)
|
103
|
+
attributes.reverse_merge!(target_attributes.attributes) && self
|
104
|
+
end
|
105
|
+
|
106
|
+
def ==(other)
|
107
|
+
other.is_a?(AttributeSet) && attributes == other.send(:attributes)
|
108
|
+
end
|
109
|
+
|
110
|
+
protected
|
111
|
+
attr_reader :attributes
|
112
|
+
|
113
|
+
private
|
114
|
+
def default_attribute(name)
|
115
|
+
Attribute.null(name)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|