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,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,41 @@
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(:cast) do |value|
9
+ if value.is_a?(Hash)
10
+ value_from_multiparameter_assignment(value)
11
+ else
12
+ super(value)
13
+ end
14
+ end
15
+
16
+ define_method(:assert_valid_value) do |value|
17
+ if value.is_a?(Hash)
18
+ value_from_multiparameter_assignment(value)
19
+ else
20
+ super(value)
21
+ end
22
+ end
23
+
24
+ define_method(:value_constructed_by_mass_assignment?) do |value|
25
+ value.is_a?(Hash)
26
+ end
27
+
28
+ define_method(:value_from_multiparameter_assignment) do |values_hash|
29
+ defaults.each do |k, v|
30
+ values_hash[k] ||= v
31
+ end
32
+ return unless values_hash[1] && values_hash[2] && values_hash[3]
33
+ values = values_hash.sort.map(&:last)
34
+ ::Time.send(default_timezone, *values)
35
+ end
36
+ private :value_from_multiparameter_assignment
37
+ end
38
+ end
39
+ end
40
+ end
41
+ 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,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ module Helpers # :nodoc: all
6
+ module Numeric
7
+ def cast(value)
8
+ value = \
9
+ case value
10
+ when true then 1
11
+ when false then 0
12
+ when ::String then value.presence
13
+ else value
14
+ end
15
+ super(value)
16
+ end
17
+
18
+ def changed?(old_value, _new_value, new_value_before_type_cast) # :nodoc:
19
+ super || number_to_non_number?(old_value, new_value_before_type_cast)
20
+ end
21
+
22
+ private
23
+
24
+ def number_to_non_number?(old_value, new_value_before_type_cast)
25
+ old_value != nil && non_numeric_string?(new_value_before_type_cast)
26
+ end
27
+
28
+ def non_numeric_string?(value)
29
+ # 'wibble'.to_i will give zero, we want to make sure
30
+ # that we aren't marking int zero to string zero as
31
+ # changed.
32
+ !/\A[-+]?\d+/.match?(value.to_s)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,68 @@
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?(:usec)
26
+ number_of_insignificant_digits = 6 - precision
27
+ round_power = 10**number_of_insignificant_digits
28
+ value.change(usec: value.usec - value.usec % round_power)
29
+ end
30
+
31
+ def type_cast_for_schema(value)
32
+ value.to_s(:db).inspect
33
+ end
34
+
35
+ def user_input_in_time_zone(value)
36
+ value.in_time_zone
37
+ end
38
+
39
+ private
40
+
41
+ def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
42
+ # Treat 0000-00-00 00:00:00 as nil.
43
+ return if year.nil? || (year == 0 && mon == 0 && mday == 0)
44
+
45
+ if offset
46
+ time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
47
+ return unless time
48
+
49
+ time -= offset
50
+ is_utc? ? time : time.getlocal
51
+ else
52
+ ::Time.public_send(default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
53
+ end
54
+ end
55
+
56
+ ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
57
+
58
+ # Doesn't handle time zones.
59
+ def fast_string_to_time(string)
60
+ if string =~ ISO_DATETIME
61
+ microsec = ($7.to_r * 1_000_000).to_i
62
+ new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ 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,70 @@
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.nil?
23
+ value.to_i
24
+ end
25
+
26
+ def serialize(value)
27
+ result = cast(value)
28
+ if result
29
+ ensure_in_range(result)
30
+ end
31
+ result
32
+ end
33
+
34
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
35
+ # Workaround for Ruby 2.2 "private attribute?" warning.
36
+ protected
37
+
38
+ attr_reader :range
39
+
40
+ private
41
+
42
+ def cast_value(value)
43
+ case value
44
+ when true then 1
45
+ when false then 0
46
+ else
47
+ value.to_i rescue nil
48
+ end
49
+ end
50
+
51
+ def ensure_in_range(value)
52
+ unless range.cover?(value)
53
+ raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit} bytes"
54
+ end
55
+ end
56
+
57
+ def max_value
58
+ 1 << (_limit * 8 - 1) # 8 bits per byte with one bit for sign
59
+ end
60
+
61
+ def min_value
62
+ -max_value
63
+ end
64
+
65
+ def _limit
66
+ limit || DEFAULT_LIMIT
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,70 @@
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
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
27
+ # Workaround for Ruby 2.2 "private attribute?" warning.
28
+ protected
29
+
30
+ attr_reader :registrations
31
+
32
+ private
33
+
34
+ def registration_klass
35
+ Registration
36
+ end
37
+
38
+ def find_registration(symbol, *args)
39
+ registrations.find { |r| r.matches?(symbol, *args) }
40
+ end
41
+ end
42
+
43
+ class Registration
44
+ # Options must be taken because of https://bugs.ruby-lang.org/issues/10856
45
+ def initialize(name, block, **)
46
+ @name = name
47
+ @block = block
48
+ end
49
+
50
+ def call(_registry, *args, **kwargs)
51
+ if kwargs.any? # https://bugs.ruby-lang.org/issues/10856
52
+ block.call(*args, **kwargs)
53
+ else
54
+ block.call(*args)
55
+ end
56
+ end
57
+
58
+ def matches?(type_name, *args, **kwargs)
59
+ type_name == name
60
+ end
61
+
62
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
63
+ # Workaround for Ruby 2.2 "private attribute?" warning.
64
+ protected
65
+
66
+ attr_reader :name, :block
67
+ end
68
+ end
69
+ # :startdoc:
70
+ 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".freeze
20
+ when false then "f".freeze
21
+ else value.to_s
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,51 @@
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 => 1970, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
10
+ )
11
+
12
+ def type
13
+ :time
14
+ end
15
+
16
+ def serialize(value)
17
+ super(cast(value))
18
+ end
19
+
20
+ def user_input_in_time_zone(value)
21
+ return unless value.present?
22
+
23
+ case value
24
+ when ::String
25
+ value = "2000-01-01 #{value}"
26
+ time_hash = ::Date._parse(value)
27
+ return if time_hash[:hour].nil?
28
+ when ::Time
29
+ value = value.change(year: 2000, day: 1, month: 1)
30
+ end
31
+
32
+ super(value)
33
+ end
34
+
35
+ private
36
+
37
+ def cast_value(value)
38
+ return apply_seconds_precision(value) unless value.is_a?(::String)
39
+ return if value.empty?
40
+
41
+ dummy_time_value = value.sub(/\A(\d\d\d\d-\d\d-\d\d |)/, "2000-01-01 ")
42
+
43
+ fast_string_to_time(dummy_time_value) || begin
44
+ time_hash = ::Date._parse(dummy_time_value)
45
+ return if time_hash[:hour].nil?
46
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end