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