omg-activemodel 8.0.0.alpha1

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