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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/type/helpers/accepts_multiparameter_time"
4
+ require "active_model/type/helpers/numeric"
5
+ require "active_model/type/helpers/mutable"
6
+ require "active_model/type/helpers/time_value"
7
+ require "active_model/type/helpers/timezone"
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ module Helpers # :nodoc: all
6
+ class AcceptsMultiparameterTime < Module
7
+ def initialize(defaults: {})
8
+ define_method(:serialize) do |value|
9
+ super(cast(value))
10
+ end
11
+
12
+ define_method(:cast) do |value|
13
+ if value.is_a?(Hash)
14
+ value_from_multiparameter_assignment(value)
15
+ else
16
+ super(value)
17
+ end
18
+ end
19
+
20
+ define_method(:assert_valid_value) do |value|
21
+ if value.is_a?(Hash)
22
+ value_from_multiparameter_assignment(value)
23
+ else
24
+ super(value)
25
+ end
26
+ end
27
+
28
+ define_method(:value_constructed_by_mass_assignment?) do |value|
29
+ value.is_a?(Hash)
30
+ end
31
+
32
+ define_method(:value_from_multiparameter_assignment) do |values_hash|
33
+ defaults.each do |k, v|
34
+ values_hash[k] ||= v
35
+ end
36
+ return unless values_hash[1] && values_hash[2] && values_hash[3]
37
+ values = values_hash.sort.map(&:last)
38
+ ::Time.send(default_timezone, *values)
39
+ end
40
+ private :value_from_multiparameter_assignment
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ module Helpers # :nodoc: all
6
+ module Mutable
7
+ def cast(value)
8
+ deserialize(serialize(value))
9
+ end
10
+
11
+ # +raw_old_value+ will be the `_before_type_cast` version of the
12
+ # value (likely a string). +new_value+ will be the current, type
13
+ # cast value.
14
+ def changed_in_place?(raw_old_value, new_value)
15
+ raw_old_value != serialize(new_value)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ module Helpers # :nodoc: all
6
+ module Numeric
7
+ def serialize(value)
8
+ cast(value)
9
+ end
10
+
11
+ def cast(value)
12
+ value = \
13
+ case value
14
+ when true then 1
15
+ when false then 0
16
+ when ::String then value.presence
17
+ else value
18
+ end
19
+ super(value)
20
+ end
21
+
22
+ def changed?(old_value, _new_value, new_value_before_type_cast) # :nodoc:
23
+ super || number_to_non_number?(old_value, new_value_before_type_cast)
24
+ end
25
+
26
+ private
27
+
28
+ def number_to_non_number?(old_value, new_value_before_type_cast)
29
+ old_value != nil && non_numeric_string?(new_value_before_type_cast.to_s)
30
+ end
31
+
32
+ def non_numeric_string?(value)
33
+ # 'wibble'.to_i will give zero, we want to make sure
34
+ # that we aren't marking int zero to string zero as
35
+ # changed.
36
+ !NUMERIC_REGEX.match?(value)
37
+ end
38
+
39
+ NUMERIC_REGEX = /\A\s*[+-]?\d/
40
+ private_constant :NUMERIC_REGEX
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/zones"
4
+ require "active_support/core_ext/time/zones"
5
+
6
+ module ActiveModel
7
+ module Type
8
+ module Helpers # :nodoc: all
9
+ module TimeValue
10
+ def serialize(value)
11
+ value = apply_seconds_precision(value)
12
+
13
+ if value.acts_like?(:time)
14
+ zone_conversion_method = is_utc? ? :getutc : :getlocal
15
+
16
+ if value.respond_to?(zone_conversion_method)
17
+ value = value.send(zone_conversion_method)
18
+ end
19
+ end
20
+
21
+ value
22
+ end
23
+
24
+ def apply_seconds_precision(value)
25
+ return value unless precision && value.respond_to?(:nsec)
26
+
27
+ number_of_insignificant_digits = 9 - precision
28
+ round_power = 10**number_of_insignificant_digits
29
+ rounded_off_nsec = value.nsec % round_power
30
+
31
+ if rounded_off_nsec > 0
32
+ value.change(nsec: value.nsec - rounded_off_nsec)
33
+ else
34
+ value
35
+ end
36
+ end
37
+
38
+ def type_cast_for_schema(value)
39
+ value.to_s(:db).inspect
40
+ end
41
+
42
+ def user_input_in_time_zone(value)
43
+ value.in_time_zone
44
+ end
45
+
46
+ private
47
+
48
+ def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
49
+ # Treat 0000-00-00 00:00:00 as nil.
50
+ return if year.nil? || (year == 0 && mon == 0 && mday == 0)
51
+
52
+ if offset
53
+ time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
54
+ return unless time
55
+
56
+ time -= offset
57
+ is_utc? ? time : time.getlocal
58
+ else
59
+ ::Time.public_send(default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
60
+ end
61
+ end
62
+
63
+ ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
64
+
65
+ # Doesn't handle time zones.
66
+ def fast_string_to_time(string)
67
+ if string =~ ISO_DATETIME
68
+ microsec_part = $7
69
+ if microsec_part && microsec_part.start_with?(".") && microsec_part.length == 7
70
+ microsec_part[0] = ""
71
+ microsec = microsec_part.to_i
72
+ else
73
+ microsec = (microsec_part.to_r * 1_000_000).to_i
74
+ end
75
+ new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/time/zones"
4
+
5
+ module ActiveModel
6
+ module Type
7
+ module Helpers # :nodoc: all
8
+ module Timezone
9
+ def is_utc?
10
+ ::Time.zone_default.nil? || ::Time.zone_default =~ "UTC"
11
+ end
12
+
13
+ def default_timezone
14
+ is_utc? ? :utc : :local
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ class ImmutableString < Value # :nodoc:
6
+ def type
7
+ :string
8
+ end
9
+
10
+ def serialize(value)
11
+ case value
12
+ when ::Numeric, ActiveSupport::Duration then value.to_s
13
+ when true then "t"
14
+ when false then "f"
15
+ else super
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def cast_value(value)
22
+ result = \
23
+ case value
24
+ when true then "t"
25
+ when false then "f"
26
+ else value.to_s
27
+ end
28
+ result.freeze
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ class Integer < Value # :nodoc:
6
+ include Helpers::Numeric
7
+
8
+ # Column storage size in bytes.
9
+ # 4 bytes means an integer as opposed to smallint etc.
10
+ DEFAULT_LIMIT = 4
11
+
12
+ def initialize(*)
13
+ super
14
+ @range = min_value...max_value
15
+ end
16
+
17
+ def type
18
+ :integer
19
+ end
20
+
21
+ def deserialize(value)
22
+ return if value.blank?
23
+ value.to_i
24
+ end
25
+
26
+ def serialize(value)
27
+ return if value.is_a?(::String) && non_numeric_string?(value)
28
+ ensure_in_range(super)
29
+ end
30
+
31
+ private
32
+ attr_reader :range
33
+
34
+ def cast_value(value)
35
+ value.to_i rescue nil
36
+ end
37
+
38
+ def ensure_in_range(value)
39
+ if value && !range.cover?(value)
40
+ raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit} bytes"
41
+ end
42
+ value
43
+ end
44
+
45
+ def max_value
46
+ 1 << (_limit * 8 - 1) # 8 bits per byte with one bit for sign
47
+ end
48
+
49
+ def min_value
50
+ -max_value
51
+ end
52
+
53
+ def _limit
54
+ limit || DEFAULT_LIMIT
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ # :stopdoc:
5
+ module Type
6
+ class Registry
7
+ def initialize
8
+ @registrations = []
9
+ end
10
+
11
+ def register(type_name, klass = nil, **options, &block)
12
+ block ||= proc { |_, *args| klass.new(*args) }
13
+ registrations << registration_klass.new(type_name, block, **options)
14
+ end
15
+
16
+ def lookup(symbol, *args)
17
+ registration = find_registration(symbol, *args)
18
+
19
+ if registration
20
+ registration.call(self, symbol, *args)
21
+ else
22
+ raise ArgumentError, "Unknown type #{symbol.inspect}"
23
+ end
24
+ end
25
+
26
+ private
27
+ attr_reader :registrations
28
+
29
+ def registration_klass
30
+ Registration
31
+ end
32
+
33
+ def find_registration(symbol, *args)
34
+ registrations.find { |r| r.matches?(symbol, *args) }
35
+ end
36
+ end
37
+
38
+ class Registration
39
+ # Options must be taken because of https://bugs.ruby-lang.org/issues/10856
40
+ def initialize(name, block, **)
41
+ @name = name
42
+ @block = block
43
+ end
44
+
45
+ def call(_registry, *args, **kwargs)
46
+ if kwargs.any? # https://bugs.ruby-lang.org/issues/10856
47
+ block.call(*args, **kwargs)
48
+ else
49
+ block.call(*args)
50
+ end
51
+ end
52
+
53
+ def matches?(type_name, *args, **kwargs)
54
+ type_name == name
55
+ end
56
+
57
+ private
58
+ attr_reader :name, :block
59
+ end
60
+ end
61
+ # :startdoc:
62
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/type/immutable_string"
4
+
5
+ module ActiveModel
6
+ module Type
7
+ class String < ImmutableString # :nodoc:
8
+ def changed_in_place?(raw_old_value, new_value)
9
+ if new_value.is_a?(::String)
10
+ raw_old_value != new_value
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def cast_value(value)
17
+ case value
18
+ when ::String then ::String.new(value)
19
+ when true then "t"
20
+ when false then "f"
21
+ else value.to_s
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ class Time < Value # :nodoc:
6
+ include Helpers::Timezone
7
+ include Helpers::TimeValue
8
+ include Helpers::AcceptsMultiparameterTime.new(
9
+ defaults: { 1 => 2000, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
10
+ )
11
+
12
+ def type
13
+ :time
14
+ end
15
+
16
+ def user_input_in_time_zone(value)
17
+ return unless value.present?
18
+
19
+ case value
20
+ when ::String
21
+ value = "2000-01-01 #{value}"
22
+ time_hash = ::Date._parse(value)
23
+ return if time_hash[:hour].nil?
24
+ when ::Time
25
+ value = value.change(year: 2000, day: 1, month: 1)
26
+ end
27
+
28
+ super(value)
29
+ end
30
+
31
+ private
32
+
33
+ def cast_value(value)
34
+ return apply_seconds_precision(value) unless value.is_a?(::String)
35
+ return if value.empty?
36
+
37
+ dummy_time_value = value.sub(/\A(\d\d\d\d-\d\d-\d\d |)/, "2000-01-01 ")
38
+
39
+ fast_string_to_time(dummy_time_value) || begin
40
+ time_hash = ::Date._parse(dummy_time_value)
41
+ return if time_hash[:hour].nil?
42
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end