omg-activemodel 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
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