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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +67 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +266 -0
- data/lib/active_model/access.rb +16 -0
- data/lib/active_model/api.rb +99 -0
- data/lib/active_model/attribute/user_provided_default.rb +55 -0
- data/lib/active_model/attribute.rb +277 -0
- data/lib/active_model/attribute_assignment.rb +78 -0
- data/lib/active_model/attribute_methods.rb +592 -0
- data/lib/active_model/attribute_mutation_tracker.rb +189 -0
- data/lib/active_model/attribute_registration.rb +117 -0
- data/lib/active_model/attribute_set/builder.rb +182 -0
- data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
- data/lib/active_model/attribute_set.rb +118 -0
- data/lib/active_model/attributes.rb +165 -0
- data/lib/active_model/callbacks.rb +155 -0
- data/lib/active_model/conversion.rb +121 -0
- data/lib/active_model/deprecator.rb +7 -0
- data/lib/active_model/dirty.rb +416 -0
- data/lib/active_model/error.rb +208 -0
- data/lib/active_model/errors.rb +547 -0
- data/lib/active_model/forbidden_attributes_protection.rb +33 -0
- data/lib/active_model/gem_version.rb +17 -0
- data/lib/active_model/lint.rb +118 -0
- data/lib/active_model/locale/en.yml +38 -0
- data/lib/active_model/model.rb +78 -0
- data/lib/active_model/naming.rb +359 -0
- data/lib/active_model/nested_error.rb +22 -0
- data/lib/active_model/railtie.rb +24 -0
- data/lib/active_model/secure_password.rb +231 -0
- data/lib/active_model/serialization.rb +198 -0
- data/lib/active_model/serializers/json.rb +154 -0
- data/lib/active_model/translation.rb +78 -0
- data/lib/active_model/type/big_integer.rb +36 -0
- data/lib/active_model/type/binary.rb +62 -0
- data/lib/active_model/type/boolean.rb +48 -0
- data/lib/active_model/type/date.rb +78 -0
- data/lib/active_model/type/date_time.rb +88 -0
- data/lib/active_model/type/decimal.rb +107 -0
- data/lib/active_model/type/float.rb +64 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +53 -0
- data/lib/active_model/type/helpers/mutable.rb +24 -0
- data/lib/active_model/type/helpers/numeric.rb +61 -0
- data/lib/active_model/type/helpers/time_value.rb +127 -0
- data/lib/active_model/type/helpers/timezone.rb +23 -0
- data/lib/active_model/type/helpers.rb +7 -0
- data/lib/active_model/type/immutable_string.rb +71 -0
- data/lib/active_model/type/integer.rb +113 -0
- data/lib/active_model/type/registry.rb +37 -0
- data/lib/active_model/type/serialize_cast_value.rb +47 -0
- data/lib/active_model/type/string.rb +43 -0
- data/lib/active_model/type/time.rb +87 -0
- data/lib/active_model/type/value.rb +157 -0
- data/lib/active_model/type.rb +55 -0
- data/lib/active_model/validations/absence.rb +33 -0
- data/lib/active_model/validations/acceptance.rb +113 -0
- data/lib/active_model/validations/callbacks.rb +119 -0
- data/lib/active_model/validations/clusivity.rb +54 -0
- data/lib/active_model/validations/comparability.rb +18 -0
- data/lib/active_model/validations/comparison.rb +90 -0
- data/lib/active_model/validations/confirmation.rb +80 -0
- data/lib/active_model/validations/exclusion.rb +49 -0
- data/lib/active_model/validations/format.rb +112 -0
- data/lib/active_model/validations/helper_methods.rb +15 -0
- data/lib/active_model/validations/inclusion.rb +47 -0
- data/lib/active_model/validations/length.rb +130 -0
- data/lib/active_model/validations/numericality.rb +222 -0
- data/lib/active_model/validations/presence.rb +39 -0
- data/lib/active_model/validations/resolve_value.rb +26 -0
- data/lib/active_model/validations/validates.rb +175 -0
- data/lib/active_model/validations/with.rb +154 -0
- data/lib/active_model/validations.rb +489 -0
- data/lib/active_model/validator.rb +190 -0
- data/lib/active_model/version.rb +10 -0
- data/lib/active_model.rb +84 -0
- 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
|