omg-activemodel 8.0.0.alpha1

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 (77) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +67 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +266 -0
  5. data/lib/active_model/access.rb +16 -0
  6. data/lib/active_model/api.rb +99 -0
  7. data/lib/active_model/attribute/user_provided_default.rb +55 -0
  8. data/lib/active_model/attribute.rb +277 -0
  9. data/lib/active_model/attribute_assignment.rb +78 -0
  10. data/lib/active_model/attribute_methods.rb +592 -0
  11. data/lib/active_model/attribute_mutation_tracker.rb +189 -0
  12. data/lib/active_model/attribute_registration.rb +117 -0
  13. data/lib/active_model/attribute_set/builder.rb +182 -0
  14. data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
  15. data/lib/active_model/attribute_set.rb +118 -0
  16. data/lib/active_model/attributes.rb +165 -0
  17. data/lib/active_model/callbacks.rb +155 -0
  18. data/lib/active_model/conversion.rb +121 -0
  19. data/lib/active_model/deprecator.rb +7 -0
  20. data/lib/active_model/dirty.rb +416 -0
  21. data/lib/active_model/error.rb +208 -0
  22. data/lib/active_model/errors.rb +547 -0
  23. data/lib/active_model/forbidden_attributes_protection.rb +33 -0
  24. data/lib/active_model/gem_version.rb +17 -0
  25. data/lib/active_model/lint.rb +118 -0
  26. data/lib/active_model/locale/en.yml +38 -0
  27. data/lib/active_model/model.rb +78 -0
  28. data/lib/active_model/naming.rb +359 -0
  29. data/lib/active_model/nested_error.rb +22 -0
  30. data/lib/active_model/railtie.rb +24 -0
  31. data/lib/active_model/secure_password.rb +231 -0
  32. data/lib/active_model/serialization.rb +198 -0
  33. data/lib/active_model/serializers/json.rb +154 -0
  34. data/lib/active_model/translation.rb +78 -0
  35. data/lib/active_model/type/big_integer.rb +36 -0
  36. data/lib/active_model/type/binary.rb +62 -0
  37. data/lib/active_model/type/boolean.rb +48 -0
  38. data/lib/active_model/type/date.rb +78 -0
  39. data/lib/active_model/type/date_time.rb +88 -0
  40. data/lib/active_model/type/decimal.rb +107 -0
  41. data/lib/active_model/type/float.rb +64 -0
  42. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +53 -0
  43. data/lib/active_model/type/helpers/mutable.rb +24 -0
  44. data/lib/active_model/type/helpers/numeric.rb +61 -0
  45. data/lib/active_model/type/helpers/time_value.rb +127 -0
  46. data/lib/active_model/type/helpers/timezone.rb +23 -0
  47. data/lib/active_model/type/helpers.rb +7 -0
  48. data/lib/active_model/type/immutable_string.rb +71 -0
  49. data/lib/active_model/type/integer.rb +113 -0
  50. data/lib/active_model/type/registry.rb +37 -0
  51. data/lib/active_model/type/serialize_cast_value.rb +47 -0
  52. data/lib/active_model/type/string.rb +43 -0
  53. data/lib/active_model/type/time.rb +87 -0
  54. data/lib/active_model/type/value.rb +157 -0
  55. data/lib/active_model/type.rb +55 -0
  56. data/lib/active_model/validations/absence.rb +33 -0
  57. data/lib/active_model/validations/acceptance.rb +113 -0
  58. data/lib/active_model/validations/callbacks.rb +119 -0
  59. data/lib/active_model/validations/clusivity.rb +54 -0
  60. data/lib/active_model/validations/comparability.rb +18 -0
  61. data/lib/active_model/validations/comparison.rb +90 -0
  62. data/lib/active_model/validations/confirmation.rb +80 -0
  63. data/lib/active_model/validations/exclusion.rb +49 -0
  64. data/lib/active_model/validations/format.rb +112 -0
  65. data/lib/active_model/validations/helper_methods.rb +15 -0
  66. data/lib/active_model/validations/inclusion.rb +47 -0
  67. data/lib/active_model/validations/length.rb +130 -0
  68. data/lib/active_model/validations/numericality.rb +222 -0
  69. data/lib/active_model/validations/presence.rb +39 -0
  70. data/lib/active_model/validations/resolve_value.rb +26 -0
  71. data/lib/active_model/validations/validates.rb +175 -0
  72. data/lib/active_model/validations/with.rb +154 -0
  73. data/lib/active_model/validations.rb +489 -0
  74. data/lib/active_model/validator.rb +190 -0
  75. data/lib/active_model/version.rb +10 -0
  76. data/lib/active_model.rb +84 -0
  77. metadata +139 -0
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/type/integer"
4
+
5
+ module ActiveModel
6
+ module Type
7
+ # = Active Model \BigInteger \Type
8
+ #
9
+ # Attribute type for integers that can be serialized to an unlimited number
10
+ # of bytes. This type is registered under the +:big_integer+ key.
11
+ #
12
+ # class Person
13
+ # include ActiveModel::Attributes
14
+ #
15
+ # attribute :id, :big_integer
16
+ # end
17
+ #
18
+ # person = Person.new
19
+ # person.id = "18_000_000_000"
20
+ #
21
+ # person.id # => 18000000000
22
+ #
23
+ # All casting and serialization are performed in the same way as the
24
+ # standard ActiveModel::Type::Integer type.
25
+ class BigInteger < Integer
26
+ def serialize_cast_value(value) # :nodoc:
27
+ value
28
+ end
29
+
30
+ private
31
+ def max_value
32
+ ::Float::INFINITY
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ # = Active Model \Binary \Type
6
+ #
7
+ # Attribute type for representation of binary data. This type is registered
8
+ # under the +:binary+ key.
9
+ #
10
+ # Non-string values are coerced to strings using their +to_s+ method.
11
+ class Binary < Value
12
+ def type
13
+ :binary
14
+ end
15
+
16
+ def binary?
17
+ true
18
+ end
19
+
20
+ def cast(value)
21
+ if value.is_a?(Data)
22
+ value.to_s
23
+ else
24
+ value = super
25
+ value = value.b if ::String === value && value.encoding != Encoding::BINARY
26
+ value
27
+ end
28
+ end
29
+
30
+ def serialize(value)
31
+ return if value.nil?
32
+ Data.new(super)
33
+ end
34
+
35
+ def changed_in_place?(raw_old_value, value)
36
+ old_value = deserialize(raw_old_value)
37
+ old_value != value
38
+ end
39
+
40
+ class Data # :nodoc:
41
+ def initialize(value)
42
+ value = value.to_s
43
+ value = value.b unless value.encoding == Encoding::BINARY
44
+ @value = value
45
+ end
46
+
47
+ def to_s
48
+ @value
49
+ end
50
+ alias_method :to_str, :to_s
51
+
52
+ def hex
53
+ @value.unpack1("H*")
54
+ end
55
+
56
+ def ==(other)
57
+ other == to_s || super
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ # = Active Model \Boolean \Type
6
+ #
7
+ # A class that behaves like a boolean type, including rules for coercion of
8
+ # user input.
9
+ #
10
+ # - <tt>"false"</tt>, <tt>"f"</tt>, <tt>"0"</tt>, +0+ or any other value in
11
+ # +FALSE_VALUES+ will be coerced to +false+.
12
+ # - Empty strings are coerced to +nil+.
13
+ # - All other values will be coerced to +true+.
14
+ class Boolean < Value
15
+ FALSE_VALUES = [
16
+ false, 0,
17
+ "0", :"0",
18
+ "f", :f,
19
+ "F", :F,
20
+ "false", :false,
21
+ "FALSE", :FALSE,
22
+ "off", :off,
23
+ "OFF", :OFF,
24
+ ].to_set.freeze
25
+
26
+ def type # :nodoc:
27
+ :boolean
28
+ end
29
+
30
+ def serialize(value) # :nodoc:
31
+ cast(value)
32
+ end
33
+
34
+ def serialize_cast_value(value) # :nodoc:
35
+ value
36
+ end
37
+
38
+ private
39
+ def cast_value(value)
40
+ if value == ""
41
+ nil
42
+ else
43
+ !FALSE_VALUES.include?(value)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ # = Active Model \Date \Type
6
+ #
7
+ # Attribute type for date representation. It is registered under the
8
+ # +:date+ key.
9
+ #
10
+ # class Person
11
+ # include ActiveModel::Attributes
12
+ #
13
+ # attribute :birthday, :date
14
+ # end
15
+ #
16
+ # person = Person.new
17
+ # person.birthday = "1989-07-13"
18
+ #
19
+ # person.birthday.class # => Date
20
+ # person.birthday.year # => 1989
21
+ # person.birthday.month # => 7
22
+ # person.birthday.day # => 13
23
+ #
24
+ # String values are parsed using the ISO 8601 date format. Any other values
25
+ # are cast using their +to_date+ method, if it exists.
26
+ class Date < Value
27
+ include Helpers::Timezone
28
+ include Helpers::AcceptsMultiparameterTime.new
29
+
30
+ def type
31
+ :date
32
+ end
33
+
34
+ def type_cast_for_schema(value)
35
+ value.to_fs(:db).inspect
36
+ end
37
+
38
+ private
39
+ def cast_value(value)
40
+ if value.is_a?(::String)
41
+ return if value.empty?
42
+ fast_string_to_date(value) || fallback_string_to_date(value)
43
+ elsif value.respond_to?(:to_date)
44
+ value.to_date
45
+ else
46
+ value
47
+ end
48
+ end
49
+
50
+ ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
51
+ def fast_string_to_date(string)
52
+ if string =~ ISO_DATE
53
+ new_date $1.to_i, $2.to_i, $3.to_i
54
+ end
55
+ end
56
+
57
+ def fallback_string_to_date(string)
58
+ parts = begin
59
+ ::Date._parse(string, false)
60
+ rescue ArgumentError
61
+ end
62
+
63
+ new_date(*parts.values_at(:year, :mon, :mday)) if parts
64
+ end
65
+
66
+ def new_date(year, mon, mday)
67
+ unless year.nil? || (year == 0 && mon == 0 && mday == 0)
68
+ ::Date.new(year, mon, mday) rescue nil
69
+ end
70
+ end
71
+
72
+ def value_from_multiparameter_assignment(*)
73
+ time = super
74
+ time && new_date(time.year, time.mon, time.mday)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ # = Active Model \DateTime \Type
6
+ #
7
+ # Attribute type to represent dates and times. It is registered under the
8
+ # +:datetime+ key.
9
+ #
10
+ # class Event
11
+ # include ActiveModel::Attributes
12
+ #
13
+ # attribute :start, :datetime
14
+ # end
15
+ #
16
+ # event = Event.new
17
+ # event.start = "Wed, 04 Sep 2013 03:00:00 EAT"
18
+ #
19
+ # event.start.class # => Time
20
+ # event.start.year # => 2013
21
+ # event.start.month # => 9
22
+ # event.start.day # => 4
23
+ # event.start.hour # => 3
24
+ # event.start.min # => 0
25
+ # event.start.sec # => 0
26
+ # event.start.zone # => "EAT"
27
+ #
28
+ # String values are parsed using the ISO 8601 datetime format. Partial
29
+ # time-only formats are also accepted.
30
+ #
31
+ # event.start = "06:07:08+09:00"
32
+ # event.start.utc # => 1999-12-31 21:07:08 UTC
33
+ #
34
+ # The degree of sub-second precision can be customized when declaring an
35
+ # attribute:
36
+ #
37
+ # class Event
38
+ # include ActiveModel::Attributes
39
+ #
40
+ # attribute :start, :datetime, precision: 4
41
+ # end
42
+ class DateTime < Value
43
+ include Helpers::Timezone
44
+ include Helpers::AcceptsMultiparameterTime.new(
45
+ defaults: { 4 => 0, 5 => 0 }
46
+ )
47
+ include Helpers::TimeValue
48
+
49
+ def type
50
+ :datetime
51
+ end
52
+
53
+ private
54
+ def cast_value(value)
55
+ return apply_seconds_precision(value) unless value.is_a?(::String)
56
+ return if value.empty?
57
+
58
+ fast_string_to_time(value) || fallback_string_to_time(value)
59
+ end
60
+
61
+ # '0.123456' -> 123456
62
+ # '1.123456' -> 123456
63
+ def microseconds(time)
64
+ time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
65
+ end
66
+
67
+ def fallback_string_to_time(string)
68
+ time_hash = begin
69
+ ::Date._parse(string)
70
+ rescue ArgumentError
71
+ end
72
+ return unless time_hash
73
+
74
+ time_hash[:sec_fraction] = microseconds(time_hash)
75
+
76
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
77
+ end
78
+
79
+ def value_from_multiparameter_assignment(values_hash)
80
+ missing_parameters = [1, 2, 3].delete_if { |key| values_hash.key?(key) }
81
+ unless missing_parameters.empty?
82
+ raise ArgumentError, "Provided hash #{values_hash} doesn't contain necessary keys: #{missing_parameters}"
83
+ end
84
+ super
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bigdecimal/util"
4
+
5
+ module ActiveModel
6
+ module Type
7
+ # = Active Model \Decimal \Type
8
+ #
9
+ # Attribute type for decimal, high-precision floating point numeric
10
+ # representation. It is registered under the +:decimal+ key.
11
+ #
12
+ # class BagOfCoffee
13
+ # include ActiveModel::Attributes
14
+ #
15
+ # attribute :weight, :decimal
16
+ # end
17
+ #
18
+ # Numeric instances are converted to BigDecimal instances. Any other objects
19
+ # are cast using their +to_d+ method, except for blank strings, which are
20
+ # cast to +nil+. If a +to_d+ method is not defined, the object is converted
21
+ # to a string using +to_s+, which is then cast using +to_d+.
22
+ #
23
+ # bag = BagOfCoffee.new
24
+ #
25
+ # bag.weight = 0.01
26
+ # bag.weight # => 0.1e-1
27
+ #
28
+ # bag.weight = "0.01"
29
+ # bag.weight # => 0.1e-1
30
+ #
31
+ # bag.weight = ""
32
+ # bag.weight # => nil
33
+ #
34
+ # bag.weight = :arbitrary
35
+ # bag.weight # => nil (the result of `.to_s.to_d`)
36
+ #
37
+ # Decimal precision defaults to 18, and can be customized when declaring an
38
+ # attribute:
39
+ #
40
+ # class BagOfCoffee
41
+ # include ActiveModel::Attributes
42
+ #
43
+ # attribute :weight, :decimal, precision: 24
44
+ # end
45
+ class Decimal < Value
46
+ include Helpers::Numeric
47
+ BIGDECIMAL_PRECISION = 18
48
+
49
+ def type
50
+ :decimal
51
+ end
52
+
53
+ def type_cast_for_schema(value)
54
+ value.to_s.inspect
55
+ end
56
+
57
+ private
58
+ def cast_value(value)
59
+ casted_value = \
60
+ case value
61
+ when ::Float
62
+ convert_float_to_big_decimal(value)
63
+ when ::Numeric
64
+ BigDecimal(value, precision || BIGDECIMAL_PRECISION)
65
+ when ::String
66
+ begin
67
+ value.to_d
68
+ rescue ArgumentError
69
+ BigDecimal(0)
70
+ end
71
+ else
72
+ if value.respond_to?(:to_d)
73
+ value.to_d
74
+ else
75
+ cast_value(value.to_s)
76
+ end
77
+ end
78
+
79
+ apply_scale(casted_value)
80
+ end
81
+
82
+ def convert_float_to_big_decimal(value)
83
+ if precision
84
+ BigDecimal(apply_scale(value), float_precision)
85
+ else
86
+ value.to_d
87
+ end
88
+ end
89
+
90
+ def float_precision
91
+ if precision.to_i > ::Float::DIG + 1
92
+ ::Float::DIG + 1
93
+ else
94
+ precision.to_i
95
+ end
96
+ end
97
+
98
+ def apply_scale(value)
99
+ if scale
100
+ value.round(scale)
101
+ else
102
+ value
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/try"
4
+
5
+ module ActiveModel
6
+ module Type
7
+ # = Active Model \Float \Type
8
+ #
9
+ # Attribute type for floating point numeric values. It is registered under
10
+ # the +:float+ key.
11
+ #
12
+ # class BagOfCoffee
13
+ # include ActiveModel::Attributes
14
+ #
15
+ # attribute :weight, :float
16
+ # end
17
+ #
18
+ # Values are cast using their +to_f+ method, except for the following
19
+ # strings:
20
+ #
21
+ # - Blank strings are cast to +nil+.
22
+ # - <tt>"Infinity"</tt> is cast to +Float::INFINITY+.
23
+ # - <tt>"-Infinity"</tt> is cast to <tt>-Float::INFINITY</tt>.
24
+ # - <tt>"NaN"</tt> is cast to +Float::NAN+.
25
+ #
26
+ # bag = BagOfCoffee.new
27
+ #
28
+ # bag.weight = "0.25"
29
+ # bag.weight # => 0.25
30
+ #
31
+ # bag.weight = ""
32
+ # bag.weight # => nil
33
+ #
34
+ # bag.weight = "NaN"
35
+ # bag.weight # => Float::NAN
36
+ class Float < Value
37
+ include Helpers::Numeric
38
+
39
+ def type
40
+ :float
41
+ end
42
+
43
+ def type_cast_for_schema(value)
44
+ return "::Float::NAN" if value.try(:nan?)
45
+ case value
46
+ when ::Float::INFINITY then "::Float::INFINITY"
47
+ when -::Float::INFINITY then "-::Float::INFINITY"
48
+ else super
49
+ end
50
+ end
51
+
52
+ private
53
+ def cast_value(value)
54
+ case value
55
+ when ::Float then value
56
+ when "Infinity" then ::Float::INFINITY
57
+ when "-Infinity" then -::Float::INFINITY
58
+ when "NaN" then ::Float::NAN
59
+ else value.to_f
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ module Helpers # :nodoc: all
6
+ class AcceptsMultiparameterTime < Module
7
+ module InstanceMethods
8
+ def serialize(value)
9
+ serialize_cast_value(cast(value))
10
+ end
11
+
12
+ def serialize_cast_value(value)
13
+ value
14
+ end
15
+
16
+ def cast(value)
17
+ if value.is_a?(Hash)
18
+ value_from_multiparameter_assignment(value)
19
+ else
20
+ super(value)
21
+ end
22
+ end
23
+
24
+ def assert_valid_value(value)
25
+ if value.is_a?(Hash)
26
+ value_from_multiparameter_assignment(value)
27
+ else
28
+ super(value)
29
+ end
30
+ end
31
+
32
+ def value_constructed_by_mass_assignment?(value)
33
+ value.is_a?(Hash)
34
+ end
35
+ end
36
+
37
+ def initialize(defaults: {})
38
+ include InstanceMethods
39
+
40
+ define_method(:value_from_multiparameter_assignment) do |values_hash|
41
+ defaults.each do |k, v|
42
+ values_hash[k] ||= v
43
+ end
44
+ return unless values_hash[1] && values_hash[2] && values_hash[3]
45
+ values = values_hash.sort.map!(&:last)
46
+ ::Time.public_send(default_timezone, *values)
47
+ end
48
+ private :value_from_multiparameter_assignment
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,24 @@
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
+
18
+ def mutable? # :nodoc:
19
+ true
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,61 @@
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 serialize_cast_value(value)
12
+ value
13
+ end
14
+
15
+ def cast(value)
16
+ # Checks whether the value is numeric. Spaceship operator
17
+ # will return nil if value is not numeric.
18
+ value = if value <=> 0
19
+ value
20
+ else
21
+ case value
22
+ when true then 1
23
+ when false then 0
24
+ else value.presence
25
+ end
26
+ end
27
+
28
+ super(value)
29
+ end
30
+
31
+ def changed?(old_value, _new_value, new_value_before_type_cast) # :nodoc:
32
+ (super || number_to_non_number?(old_value, new_value_before_type_cast)) &&
33
+ !equal_nan?(old_value, new_value_before_type_cast)
34
+ end
35
+
36
+ private
37
+ def equal_nan?(old_value, new_value)
38
+ (old_value.is_a?(::Float) || old_value.is_a?(BigDecimal)) &&
39
+ old_value.nan? &&
40
+ old_value.instance_of?(new_value.class) &&
41
+ new_value.nan?
42
+ end
43
+
44
+ def number_to_non_number?(old_value, new_value_before_type_cast)
45
+ old_value != nil && !new_value_before_type_cast.is_a?(::Numeric) &&
46
+ non_numeric_string?(new_value_before_type_cast.to_s)
47
+ end
48
+
49
+ def non_numeric_string?(value)
50
+ # 'wibble'.to_i will give zero, we want to make sure
51
+ # that we aren't marking int zero to string zero as
52
+ # changed.
53
+ !NUMERIC_REGEX.match?(value)
54
+ end
55
+
56
+ NUMERIC_REGEX = /\A\s*[+-]?\d/
57
+ private_constant :NUMERIC_REGEX
58
+ end
59
+ end
60
+ end
61
+ end