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.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +172 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +266 -0
  5. data/lib/active_model.rb +77 -0
  6. data/lib/active_model/attribute.rb +247 -0
  7. data/lib/active_model/attribute/user_provided_default.rb +51 -0
  8. data/lib/active_model/attribute_assignment.rb +57 -0
  9. data/lib/active_model/attribute_methods.rb +517 -0
  10. data/lib/active_model/attribute_mutation_tracker.rb +178 -0
  11. data/lib/active_model/attribute_set.rb +106 -0
  12. data/lib/active_model/attribute_set/builder.rb +124 -0
  13. data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
  14. data/lib/active_model/attributes.rb +138 -0
  15. data/lib/active_model/callbacks.rb +156 -0
  16. data/lib/active_model/conversion.rb +111 -0
  17. data/lib/active_model/dirty.rb +280 -0
  18. data/lib/active_model/errors.rb +601 -0
  19. data/lib/active_model/forbidden_attributes_protection.rb +31 -0
  20. data/lib/active_model/gem_version.rb +17 -0
  21. data/lib/active_model/lint.rb +118 -0
  22. data/lib/active_model/locale/en.yml +36 -0
  23. data/lib/active_model/model.rb +99 -0
  24. data/lib/active_model/naming.rb +334 -0
  25. data/lib/active_model/railtie.rb +20 -0
  26. data/lib/active_model/secure_password.rb +128 -0
  27. data/lib/active_model/serialization.rb +192 -0
  28. data/lib/active_model/serializers/json.rb +147 -0
  29. data/lib/active_model/translation.rb +70 -0
  30. data/lib/active_model/type.rb +53 -0
  31. data/lib/active_model/type/big_integer.rb +15 -0
  32. data/lib/active_model/type/binary.rb +52 -0
  33. data/lib/active_model/type/boolean.rb +47 -0
  34. data/lib/active_model/type/date.rb +53 -0
  35. data/lib/active_model/type/date_time.rb +47 -0
  36. data/lib/active_model/type/decimal.rb +70 -0
  37. data/lib/active_model/type/float.rb +34 -0
  38. data/lib/active_model/type/helpers.rb +7 -0
  39. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +45 -0
  40. data/lib/active_model/type/helpers/mutable.rb +20 -0
  41. data/lib/active_model/type/helpers/numeric.rb +44 -0
  42. data/lib/active_model/type/helpers/time_value.rb +81 -0
  43. data/lib/active_model/type/helpers/timezone.rb +19 -0
  44. data/lib/active_model/type/immutable_string.rb +32 -0
  45. data/lib/active_model/type/integer.rb +58 -0
  46. data/lib/active_model/type/registry.rb +62 -0
  47. data/lib/active_model/type/string.rb +26 -0
  48. data/lib/active_model/type/time.rb +47 -0
  49. data/lib/active_model/type/value.rb +126 -0
  50. data/lib/active_model/validations.rb +437 -0
  51. data/lib/active_model/validations/absence.rb +33 -0
  52. data/lib/active_model/validations/acceptance.rb +102 -0
  53. data/lib/active_model/validations/callbacks.rb +122 -0
  54. data/lib/active_model/validations/clusivity.rb +54 -0
  55. data/lib/active_model/validations/confirmation.rb +80 -0
  56. data/lib/active_model/validations/exclusion.rb +49 -0
  57. data/lib/active_model/validations/format.rb +114 -0
  58. data/lib/active_model/validations/helper_methods.rb +15 -0
  59. data/lib/active_model/validations/inclusion.rb +47 -0
  60. data/lib/active_model/validations/length.rb +129 -0
  61. data/lib/active_model/validations/numericality.rb +189 -0
  62. data/lib/active_model/validations/presence.rb +39 -0
  63. data/lib/active_model/validations/validates.rb +174 -0
  64. data/lib/active_model/validations/with.rb +147 -0
  65. data/lib/active_model/validator.rb +183 -0
  66. data/lib/active_model/version.rb +10 -0
  67. 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