activemodel 5.2.3

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 +114 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +264 -0
  5. data/lib/active_model.rb +77 -0
  6. data/lib/active_model/attribute.rb +248 -0
  7. data/lib/active_model/attribute/user_provided_default.rb +52 -0
  8. data/lib/active_model/attribute_assignment.rb +57 -0
  9. data/lib/active_model/attribute_methods.rb +478 -0
  10. data/lib/active_model/attribute_mutation_tracker.rb +124 -0
  11. data/lib/active_model/attribute_set.rb +114 -0
  12. data/lib/active_model/attribute_set/builder.rb +126 -0
  13. data/lib/active_model/attribute_set/yaml_encoder.rb +41 -0
  14. data/lib/active_model/attributes.rb +111 -0
  15. data/lib/active_model/callbacks.rb +153 -0
  16. data/lib/active_model/conversion.rb +111 -0
  17. data/lib/active_model/dirty.rb +343 -0
  18. data/lib/active_model/errors.rb +517 -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 +318 -0
  25. data/lib/active_model/railtie.rb +14 -0
  26. data/lib/active_model/secure_password.rb +129 -0
  27. data/lib/active_model/serialization.rb +192 -0
  28. data/lib/active_model/serializers/json.rb +146 -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 +38 -0
  34. data/lib/active_model/type/date.rb +57 -0
  35. data/lib/active_model/type/date_time.rb +51 -0
  36. data/lib/active_model/type/decimal.rb +70 -0
  37. data/lib/active_model/type/float.rb +36 -0
  38. data/lib/active_model/type/helpers.rb +7 -0
  39. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +41 -0
  40. data/lib/active_model/type/helpers/mutable.rb +20 -0
  41. data/lib/active_model/type/helpers/numeric.rb +37 -0
  42. data/lib/active_model/type/helpers/time_value.rb +68 -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 +70 -0
  46. data/lib/active_model/type/registry.rb +70 -0
  47. data/lib/active_model/type/string.rb +26 -0
  48. data/lib/active_model/type/time.rb +51 -0
  49. data/lib/active_model/type/value.rb +126 -0
  50. data/lib/active_model/validations.rb +439 -0
  51. data/lib/active_model/validations/absence.rb +33 -0
  52. data/lib/active_model/validations/acceptance.rb +106 -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,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/indifferent_access"
4
+
5
+ module ActiveModel
6
+ class AttributeMutationTracker # :nodoc:
7
+ OPTION_NOT_GIVEN = Object.new
8
+
9
+ def initialize(attributes)
10
+ @attributes = attributes
11
+ @forced_changes = Set.new
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] = attributes[attr_name].original_value
22
+ end
23
+ end
24
+ end
25
+
26
+ def changes
27
+ attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
28
+ change = change_to_attribute(attr_name)
29
+ if change
30
+ result.merge!(attr_name => change)
31
+ end
32
+ end
33
+ end
34
+
35
+ def change_to_attribute(attr_name)
36
+ attr_name = attr_name.to_s
37
+ if changed?(attr_name)
38
+ [attributes[attr_name].original_value, attributes.fetch_value(attr_name)]
39
+ end
40
+ end
41
+
42
+ def any_changes?
43
+ attr_names.any? { |attr| changed?(attr) }
44
+ end
45
+
46
+ def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN)
47
+ attr_name = attr_name.to_s
48
+ forced_changes.include?(attr_name) ||
49
+ attributes[attr_name].changed? &&
50
+ (OPTION_NOT_GIVEN == from || attributes[attr_name].original_value == from) &&
51
+ (OPTION_NOT_GIVEN == to || attributes[attr_name].value == to)
52
+ end
53
+
54
+ def changed_in_place?(attr_name)
55
+ attributes[attr_name.to_s].changed_in_place?
56
+ end
57
+
58
+ def forget_change(attr_name)
59
+ attr_name = attr_name.to_s
60
+ attributes[attr_name] = attributes[attr_name].forgetting_assignment
61
+ forced_changes.delete(attr_name)
62
+ end
63
+
64
+ def original_value(attr_name)
65
+ attributes[attr_name.to_s].original_value
66
+ end
67
+
68
+ def force_change(attr_name)
69
+ forced_changes << attr_name.to_s
70
+ end
71
+
72
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
73
+ # Workaround for Ruby 2.2 "private attribute?" warning.
74
+ protected
75
+
76
+ attr_reader :attributes, :forced_changes
77
+
78
+ private
79
+
80
+ def attr_names
81
+ attributes.keys
82
+ end
83
+ end
84
+
85
+ class NullMutationTracker # :nodoc:
86
+ include Singleton
87
+
88
+ def changed_attribute_names(*)
89
+ []
90
+ end
91
+
92
+ def changed_values(*)
93
+ {}
94
+ end
95
+
96
+ def changes(*)
97
+ {}
98
+ end
99
+
100
+ def change_to_attribute(attr_name)
101
+ end
102
+
103
+ def any_changes?(*)
104
+ false
105
+ end
106
+
107
+ def changed?(*)
108
+ false
109
+ end
110
+
111
+ def changed_in_place?(*)
112
+ false
113
+ end
114
+
115
+ def forget_change(*)
116
+ end
117
+
118
+ def original_value(*)
119
+ end
120
+
121
+ def force_change(*)
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,114 @@
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
+ if defined?(JRUBY_VERSION)
41
+ # This form is significantly faster on JRuby, and this is one of our biggest hotspots.
42
+ # https://github.com/jruby/jruby/pull/2562
43
+ def fetch_value(name, &block)
44
+ self[name].value(&block)
45
+ end
46
+ else
47
+ def fetch_value(name)
48
+ self[name].value { |n| yield n if block_given? }
49
+ end
50
+ end
51
+
52
+ def write_from_database(name, value)
53
+ attributes[name] = self[name].with_value_from_database(value)
54
+ end
55
+
56
+ def write_from_user(name, value)
57
+ attributes[name] = self[name].with_value_from_user(value)
58
+ end
59
+
60
+ def write_cast_value(name, value)
61
+ attributes[name] = self[name].with_cast_value(value)
62
+ end
63
+
64
+ def freeze
65
+ @attributes.freeze
66
+ super
67
+ end
68
+
69
+ def deep_dup
70
+ self.class.allocate.tap do |copy|
71
+ copy.instance_variable_set(:@attributes, attributes.deep_dup)
72
+ end
73
+ end
74
+
75
+ def initialize_dup(_)
76
+ @attributes = attributes.dup
77
+ super
78
+ end
79
+
80
+ def initialize_clone(_)
81
+ @attributes = attributes.clone
82
+ super
83
+ end
84
+
85
+ def reset(key)
86
+ if key?(key)
87
+ write_from_database(key, nil)
88
+ end
89
+ end
90
+
91
+ def accessed
92
+ attributes.select { |_, attr| attr.has_been_read? }.keys
93
+ end
94
+
95
+ def map(&block)
96
+ new_attributes = attributes.transform_values(&block)
97
+ AttributeSet.new(new_attributes)
98
+ end
99
+
100
+ def ==(other)
101
+ attributes == other.attributes
102
+ end
103
+
104
+ protected
105
+
106
+ attr_reader :attributes
107
+
108
+ private
109
+
110
+ def initialized_attributes
111
+ attributes.select { |_, attr| attr.initialized? }
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,126 @@
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
+
94
+ attr_reader :types, :values, :additional_types, :delegate_hash, :default_attributes
95
+
96
+ def materialize
97
+ unless @materialized
98
+ values.each_key { |key| self[key] }
99
+ types.each_key { |key| self[key] }
100
+ unless frozen?
101
+ @materialized = true
102
+ end
103
+ end
104
+ delegate_hash
105
+ end
106
+
107
+ private
108
+
109
+ def assign_default_value(name)
110
+ type = additional_types.fetch(name, types[name])
111
+ value_present = true
112
+ value = values.fetch(name) { value_present = false }
113
+
114
+ if value_present
115
+ delegate_hash[name] = Attribute.from_database(name, value, type)
116
+ elsif types.key?(name)
117
+ attr = default_attributes[name]
118
+ if attr
119
+ delegate_hash[name] = attr.dup
120
+ else
121
+ delegate_hash[name] = Attribute.uninitialized(name, type)
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,41 @@
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
+ protected
37
+
38
+ attr_reader :default_types
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/attribute_set"
4
+ require "active_model/attribute/user_provided_default"
5
+
6
+ module ActiveModel
7
+ module Attributes #:nodoc:
8
+ extend ActiveSupport::Concern
9
+ include ActiveModel::AttributeMethods
10
+
11
+ included do
12
+ attribute_method_suffix "="
13
+ class_attribute :attribute_types, :_default_attributes, instance_accessor: false
14
+ self.attribute_types = Hash.new(Type.default_value)
15
+ self._default_attributes = AttributeSet.new({})
16
+ end
17
+
18
+ module ClassMethods
19
+ def attribute(name, type = Type::Value.new, **options)
20
+ name = name.to_s
21
+ if type.is_a?(Symbol)
22
+ type = ActiveModel::Type.lookup(type, **options.except(:default))
23
+ end
24
+ self.attribute_types = attribute_types.merge(name => type)
25
+ define_default_attribute(name, options.fetch(:default, NO_DEFAULT_PROVIDED), type)
26
+ define_attribute_method(name)
27
+ end
28
+
29
+ private
30
+
31
+ def define_method_attribute=(name)
32
+ safe_name = name.unpack("h*".freeze).first
33
+ ActiveModel::AttributeMethods::AttrNames.set_name_cache safe_name, name
34
+
35
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
36
+ def __temp__#{safe_name}=(value)
37
+ name = ::ActiveModel::AttributeMethods::AttrNames::ATTR_#{safe_name}
38
+ write_attribute(name, value)
39
+ end
40
+ alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
41
+ undef_method :__temp__#{safe_name}=
42
+ STR
43
+ end
44
+
45
+ NO_DEFAULT_PROVIDED = Object.new # :nodoc:
46
+ private_constant :NO_DEFAULT_PROVIDED
47
+
48
+ def define_default_attribute(name, value, type)
49
+ self._default_attributes = _default_attributes.deep_dup
50
+ if value == NO_DEFAULT_PROVIDED
51
+ default_attribute = _default_attributes[name].with_type(type)
52
+ else
53
+ default_attribute = Attribute::UserProvidedDefault.new(
54
+ name,
55
+ value,
56
+ type,
57
+ _default_attributes.fetch(name.to_s) { nil },
58
+ )
59
+ end
60
+ _default_attributes[name] = default_attribute
61
+ end
62
+ end
63
+
64
+ def initialize(*)
65
+ @attributes = self.class._default_attributes.deep_dup
66
+ super
67
+ end
68
+
69
+ def attributes
70
+ @attributes.to_hash
71
+ end
72
+
73
+ private
74
+
75
+ def write_attribute(attr_name, value)
76
+ name = if self.class.attribute_alias?(attr_name)
77
+ self.class.attribute_alias(attr_name).to_s
78
+ else
79
+ attr_name.to_s
80
+ end
81
+
82
+ @attributes.write_from_user(name, value)
83
+ value
84
+ end
85
+
86
+ def attribute(attr_name)
87
+ name = if self.class.attribute_alias?(attr_name)
88
+ self.class.attribute_alias(attr_name).to_s
89
+ else
90
+ attr_name.to_s
91
+ end
92
+ @attributes.fetch_value(name)
93
+ end
94
+
95
+ # Handle *= for method_missing.
96
+ def attribute=(attribute_name, value)
97
+ write_attribute(attribute_name, value)
98
+ end
99
+ end
100
+
101
+ module AttributeMethods #:nodoc:
102
+ AttrNames = Module.new {
103
+ def self.set_name_cache(name, value)
104
+ const_name = "ATTR_#{name}"
105
+ unless const_defined? const_name
106
+ const_set const_name, value.dup.freeze
107
+ end
108
+ end
109
+ }
110
+ end
111
+ end