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.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +67 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +266 -0
  5. data/lib/active_model/access.rb +16 -0
  6. data/lib/active_model/api.rb +99 -0
  7. data/lib/active_model/attribute/user_provided_default.rb +55 -0
  8. data/lib/active_model/attribute.rb +277 -0
  9. data/lib/active_model/attribute_assignment.rb +78 -0
  10. data/lib/active_model/attribute_methods.rb +592 -0
  11. data/lib/active_model/attribute_mutation_tracker.rb +189 -0
  12. data/lib/active_model/attribute_registration.rb +117 -0
  13. data/lib/active_model/attribute_set/builder.rb +182 -0
  14. data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
  15. data/lib/active_model/attribute_set.rb +118 -0
  16. data/lib/active_model/attributes.rb +165 -0
  17. data/lib/active_model/callbacks.rb +155 -0
  18. data/lib/active_model/conversion.rb +121 -0
  19. data/lib/active_model/deprecator.rb +7 -0
  20. data/lib/active_model/dirty.rb +416 -0
  21. data/lib/active_model/error.rb +208 -0
  22. data/lib/active_model/errors.rb +547 -0
  23. data/lib/active_model/forbidden_attributes_protection.rb +33 -0
  24. data/lib/active_model/gem_version.rb +17 -0
  25. data/lib/active_model/lint.rb +118 -0
  26. data/lib/active_model/locale/en.yml +38 -0
  27. data/lib/active_model/model.rb +78 -0
  28. data/lib/active_model/naming.rb +359 -0
  29. data/lib/active_model/nested_error.rb +22 -0
  30. data/lib/active_model/railtie.rb +24 -0
  31. data/lib/active_model/secure_password.rb +231 -0
  32. data/lib/active_model/serialization.rb +198 -0
  33. data/lib/active_model/serializers/json.rb +154 -0
  34. data/lib/active_model/translation.rb +78 -0
  35. data/lib/active_model/type/big_integer.rb +36 -0
  36. data/lib/active_model/type/binary.rb +62 -0
  37. data/lib/active_model/type/boolean.rb +48 -0
  38. data/lib/active_model/type/date.rb +78 -0
  39. data/lib/active_model/type/date_time.rb +88 -0
  40. data/lib/active_model/type/decimal.rb +107 -0
  41. data/lib/active_model/type/float.rb +64 -0
  42. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +53 -0
  43. data/lib/active_model/type/helpers/mutable.rb +24 -0
  44. data/lib/active_model/type/helpers/numeric.rb +61 -0
  45. data/lib/active_model/type/helpers/time_value.rb +127 -0
  46. data/lib/active_model/type/helpers/timezone.rb +23 -0
  47. data/lib/active_model/type/helpers.rb +7 -0
  48. data/lib/active_model/type/immutable_string.rb +71 -0
  49. data/lib/active_model/type/integer.rb +113 -0
  50. data/lib/active_model/type/registry.rb +37 -0
  51. data/lib/active_model/type/serialize_cast_value.rb +47 -0
  52. data/lib/active_model/type/string.rb +43 -0
  53. data/lib/active_model/type/time.rb +87 -0
  54. data/lib/active_model/type/value.rb +157 -0
  55. data/lib/active_model/type.rb +55 -0
  56. data/lib/active_model/validations/absence.rb +33 -0
  57. data/lib/active_model/validations/acceptance.rb +113 -0
  58. data/lib/active_model/validations/callbacks.rb +119 -0
  59. data/lib/active_model/validations/clusivity.rb +54 -0
  60. data/lib/active_model/validations/comparability.rb +18 -0
  61. data/lib/active_model/validations/comparison.rb +90 -0
  62. data/lib/active_model/validations/confirmation.rb +80 -0
  63. data/lib/active_model/validations/exclusion.rb +49 -0
  64. data/lib/active_model/validations/format.rb +112 -0
  65. data/lib/active_model/validations/helper_methods.rb +15 -0
  66. data/lib/active_model/validations/inclusion.rb +47 -0
  67. data/lib/active_model/validations/length.rb +130 -0
  68. data/lib/active_model/validations/numericality.rb +222 -0
  69. data/lib/active_model/validations/presence.rb +39 -0
  70. data/lib/active_model/validations/resolve_value.rb +26 -0
  71. data/lib/active_model/validations/validates.rb +175 -0
  72. data/lib/active_model/validations/with.rb +154 -0
  73. data/lib/active_model/validations.rb +489 -0
  74. data/lib/active_model/validator.rb +190 -0
  75. data/lib/active_model/version.rb +10 -0
  76. data/lib/active_model.rb +84 -0
  77. 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