activemodel 6.0.0

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