activemodel 7.0.8.1 → 7.1.0.beta1
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 +4 -4
- data/CHANGELOG.md +132 -196
- data/MIT-LICENSE +1 -1
- data/README.rdoc +9 -9
- data/lib/active_model/access.rb +16 -0
- data/lib/active_model/api.rb +5 -5
- data/lib/active_model/attribute/user_provided_default.rb +4 -0
- data/lib/active_model/attribute.rb +26 -1
- data/lib/active_model/attribute_assignment.rb +1 -1
- data/lib/active_model/attribute_methods.rb +102 -63
- data/lib/active_model/attribute_registration.rb +77 -0
- data/lib/active_model/attribute_set.rb +9 -0
- data/lib/active_model/attributes.rb +62 -45
- data/lib/active_model/callbacks.rb +5 -5
- data/lib/active_model/conversion.rb +14 -4
- data/lib/active_model/deprecator.rb +7 -0
- data/lib/active_model/dirty.rb +134 -13
- data/lib/active_model/error.rb +4 -3
- data/lib/active_model/errors.rb +17 -12
- data/lib/active_model/forbidden_attributes_protection.rb +2 -0
- data/lib/active_model/gem_version.rb +4 -4
- data/lib/active_model/lint.rb +1 -1
- data/lib/active_model/locale/en.yml +4 -3
- data/lib/active_model/model.rb +26 -2
- data/lib/active_model/naming.rb +29 -10
- data/lib/active_model/railtie.rb +4 -0
- data/lib/active_model/secure_password.rb +61 -23
- data/lib/active_model/serialization.rb +3 -3
- data/lib/active_model/serializers/json.rb +1 -1
- data/lib/active_model/translation.rb +18 -16
- data/lib/active_model/type/big_integer.rb +23 -1
- data/lib/active_model/type/binary.rb +7 -1
- data/lib/active_model/type/boolean.rb +11 -9
- data/lib/active_model/type/date.rb +28 -2
- data/lib/active_model/type/date_time.rb +45 -3
- data/lib/active_model/type/decimal.rb +39 -1
- data/lib/active_model/type/float.rb +30 -1
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +5 -1
- data/lib/active_model/type/helpers/numeric.rb +4 -0
- data/lib/active_model/type/helpers/time_value.rb +28 -12
- data/lib/active_model/type/immutable_string.rb +37 -1
- data/lib/active_model/type/integer.rb +44 -1
- data/lib/active_model/type/serialize_cast_value.rb +47 -0
- data/lib/active_model/type/string.rb +9 -1
- data/lib/active_model/type/time.rb +48 -7
- data/lib/active_model/type/value.rb +17 -1
- data/lib/active_model/type.rb +1 -0
- data/lib/active_model/validations/absence.rb +1 -1
- data/lib/active_model/validations/acceptance.rb +1 -1
- data/lib/active_model/validations/callbacks.rb +4 -4
- data/lib/active_model/validations/clusivity.rb +5 -8
- data/lib/active_model/validations/comparability.rb +0 -11
- data/lib/active_model/validations/comparison.rb +15 -7
- data/lib/active_model/validations/confirmation.rb +1 -1
- data/lib/active_model/validations/format.rb +6 -7
- data/lib/active_model/validations/length.rb +10 -8
- data/lib/active_model/validations/numericality.rb +35 -23
- data/lib/active_model/validations/presence.rb +2 -2
- data/lib/active_model/validations/resolve_value.rb +26 -0
- data/lib/active_model/validations/validates.rb +4 -4
- data/lib/active_model/validations/with.rb +9 -2
- data/lib/active_model/validations.rb +45 -10
- data/lib/active_model/validator.rb +7 -5
- data/lib/active_model/version.rb +1 -1
- data/lib/active_model.rb +5 -1
- metadata +15 -10
@@ -16,8 +16,8 @@ module ActiveModel
|
|
16
16
|
|
17
17
|
module ClassMethods
|
18
18
|
# Adds methods to set and authenticate against a BCrypt password.
|
19
|
-
# This mechanism requires you to have a +XXX_digest+ attribute
|
20
|
-
#
|
19
|
+
# This mechanism requires you to have a +XXX_digest+ attribute,
|
20
|
+
# where +XXX+ is the attribute name of your desired password.
|
21
21
|
#
|
22
22
|
# The following validations are added automatically:
|
23
23
|
# * Password must be present on creation
|
@@ -29,10 +29,17 @@ module ActiveModel
|
|
29
29
|
# it). When this attribute has a +nil+ value, the validation will not be
|
30
30
|
# triggered.
|
31
31
|
#
|
32
|
-
#
|
33
|
-
#
|
32
|
+
# Additionally, a +XXX_challenge+ attribute is created. When set to a
|
33
|
+
# value other than +nil+, it will validate against the currently persisted
|
34
|
+
# password. This validation relies on dirty tracking, as provided by
|
35
|
+
# ActiveModel::Dirty; if dirty tracking methods are not defined, this
|
36
|
+
# validation will fail.
|
34
37
|
#
|
35
|
-
#
|
38
|
+
# All of the above validations can be omitted by passing
|
39
|
+
# <tt>validations: false</tt> as an argument. This allows complete
|
40
|
+
# customizability of validation behavior.
|
41
|
+
#
|
42
|
+
# To use +has_secure_password+, add bcrypt (~> 3.1.7) to your Gemfile:
|
36
43
|
#
|
37
44
|
# gem 'bcrypt', '~> 3.1.7'
|
38
45
|
#
|
@@ -46,20 +53,30 @@ module ActiveModel
|
|
46
53
|
# has_secure_password :recovery_password, validations: false
|
47
54
|
# end
|
48
55
|
#
|
49
|
-
# user = User.new(name:
|
50
|
-
#
|
51
|
-
# user.password
|
52
|
-
# user.
|
53
|
-
# user.
|
54
|
-
# user.
|
56
|
+
# user = User.new(name: "david", password: "", password_confirmation: "nomatch")
|
57
|
+
#
|
58
|
+
# user.save # => false, password required
|
59
|
+
# user.password = "vr00m"
|
60
|
+
# user.save # => false, confirmation doesn’t match
|
61
|
+
# user.password_confirmation = "vr00m"
|
62
|
+
# user.save # => true
|
63
|
+
#
|
64
|
+
# user.authenticate("notright") # => false
|
65
|
+
# user.authenticate("vr00m") # => user
|
66
|
+
# User.find_by(name: "david")&.authenticate("notright") # => false
|
67
|
+
# User.find_by(name: "david")&.authenticate("vr00m") # => user
|
68
|
+
#
|
55
69
|
# user.recovery_password = "42password"
|
56
|
-
# user.recovery_password_digest
|
57
|
-
# user.save
|
58
|
-
#
|
59
|
-
# user.
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
70
|
+
# user.recovery_password_digest # => "$2a$04$iOfhwahFymCs5weB3BNH/uXkTG65HR.qpW.bNhEjFP3ftli3o5DQC"
|
71
|
+
# user.save # => true
|
72
|
+
#
|
73
|
+
# user.authenticate_recovery_password("42password") # => user
|
74
|
+
#
|
75
|
+
# user.update(password: "pwn3d", password_challenge: "") # => false, challenge doesn't authenticate
|
76
|
+
# user.update(password: "nohack4u", password_challenge: "vr00m") # => true
|
77
|
+
#
|
78
|
+
# user.authenticate("vr00m") # => false, old password
|
79
|
+
# user.authenticate("nohack4u") # => user
|
63
80
|
#
|
64
81
|
# ===== Conditionally requiring a password
|
65
82
|
#
|
@@ -88,7 +105,7 @@ module ActiveModel
|
|
88
105
|
begin
|
89
106
|
require "bcrypt"
|
90
107
|
rescue LoadError
|
91
|
-
|
108
|
+
warn "You don't have bcrypt installed in your application. Please add it to your Gemfile and run bundle install."
|
92
109
|
raise
|
93
110
|
end
|
94
111
|
|
@@ -105,7 +122,24 @@ module ActiveModel
|
|
105
122
|
record.errors.add(attribute, :blank) unless record.public_send("#{attribute}_digest").present?
|
106
123
|
end
|
107
124
|
|
108
|
-
|
125
|
+
validate do |record|
|
126
|
+
if challenge = record.public_send(:"#{attribute}_challenge")
|
127
|
+
digest_was = record.public_send(:"#{attribute}_digest_was") if record.respond_to?(:"#{attribute}_digest_was")
|
128
|
+
|
129
|
+
unless digest_was.present? && BCrypt::Password.new(digest_was).is_password?(challenge)
|
130
|
+
record.errors.add(:"#{attribute}_challenge")
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Validates that the password does not exceed the maximum allowed bytes for BCrypt (72 bytes).
|
136
|
+
validate do |record|
|
137
|
+
password_value = record.public_send(attribute)
|
138
|
+
if password_value.present? && password_value.bytesize > ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
|
139
|
+
record.errors.add(attribute, :password_too_long)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
109
143
|
validates_confirmation_of attribute, allow_blank: true
|
110
144
|
end
|
111
145
|
end
|
@@ -126,9 +160,7 @@ module ActiveModel
|
|
126
160
|
end
|
127
161
|
end
|
128
162
|
|
129
|
-
|
130
|
-
instance_variable_set("@#{attribute}_confirmation", unencrypted_password)
|
131
|
-
end
|
163
|
+
attr_accessor :"#{attribute}_confirmation", :"#{attribute}_challenge"
|
132
164
|
|
133
165
|
# Returns +self+ if the password is correct, otherwise +false+.
|
134
166
|
#
|
@@ -145,6 +177,12 @@ module ActiveModel
|
|
145
177
|
attribute_digest.present? && BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self
|
146
178
|
end
|
147
179
|
|
180
|
+
# Returns the salt, a small chunk of random data added to the password before it's hashed.
|
181
|
+
define_method("#{attribute}_salt") do
|
182
|
+
attribute_digest = public_send("#{attribute}_digest")
|
183
|
+
attribute_digest.present? ? BCrypt::Password.new(attribute_digest).salt : nil
|
184
|
+
end
|
185
|
+
|
148
186
|
alias_method :authenticate, :authenticate_password if attribute == :password
|
149
187
|
end
|
150
188
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
require "active_support/core_ext/enumerable"
|
4
4
|
|
5
5
|
module ActiveModel
|
6
|
-
#
|
6
|
+
# = Active \Model \Serialization
|
7
7
|
#
|
8
8
|
# Provides a basic serialization to a serializable_hash for your objects.
|
9
9
|
#
|
@@ -33,8 +33,8 @@ module ActiveModel
|
|
33
33
|
# at the private method +read_attribute_for_serialization+.
|
34
34
|
#
|
35
35
|
# ActiveModel::Serializers::JSON module automatically includes
|
36
|
-
# the
|
37
|
-
# explicitly include
|
36
|
+
# the +ActiveModel::Serialization+ module, so there is no need to
|
37
|
+
# explicitly include +ActiveModel::Serialization+.
|
38
38
|
#
|
39
39
|
# A minimal implementation including JSON would be:
|
40
40
|
#
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveModel
|
4
|
-
#
|
4
|
+
# = Active \Model \Translation
|
5
5
|
#
|
6
|
-
# Provides integration between your object and the Rails internationalization
|
6
|
+
# Provides integration between your object and the \Rails internationalization
|
7
7
|
# (i18n) framework.
|
8
8
|
#
|
9
9
|
# A minimal implementation could be:
|
@@ -16,7 +16,7 @@ module ActiveModel
|
|
16
16
|
# # => "My attribute"
|
17
17
|
#
|
18
18
|
# This also provides the required class methods for hooking into the
|
19
|
-
# Rails internationalization API, including being able to define a
|
19
|
+
# \Rails internationalization API, including being able to define a
|
20
20
|
# class-based +i18n_scope+ and +lookup_ancestors+ to find translations in
|
21
21
|
# parent classes.
|
22
22
|
module Translation
|
@@ -35,6 +35,8 @@ module ActiveModel
|
|
35
35
|
ancestors.select { |x| x.respond_to?(:model_name) }
|
36
36
|
end
|
37
37
|
|
38
|
+
MISSING_TRANSLATION = -(2**60) # :nodoc:
|
39
|
+
|
38
40
|
# Transforms attribute names into a more human format, such as "First name"
|
39
41
|
# instead of "first_name".
|
40
42
|
#
|
@@ -42,29 +44,29 @@ module ActiveModel
|
|
42
44
|
#
|
43
45
|
# Specify +options+ with additional translating options.
|
44
46
|
def human_attribute_name(attribute, options = {})
|
45
|
-
|
46
|
-
|
47
|
-
attribute
|
48
|
-
|
49
|
-
|
47
|
+
attribute = attribute.to_s
|
48
|
+
|
49
|
+
if attribute.include?(".")
|
50
|
+
namespace, _, attribute = attribute.rpartition(".")
|
51
|
+
namespace.tr!(".", "/")
|
50
52
|
|
51
|
-
if namespace
|
52
53
|
defaults = lookup_ancestors.map do |klass|
|
53
|
-
:"#{
|
54
|
+
:"#{i18n_scope}.attributes.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}"
|
54
55
|
end
|
55
|
-
defaults << :"#{
|
56
|
+
defaults << :"#{i18n_scope}.attributes.#{namespace}.#{attribute}"
|
56
57
|
else
|
57
58
|
defaults = lookup_ancestors.map do |klass|
|
58
|
-
:"#{
|
59
|
+
:"#{i18n_scope}.attributes.#{klass.model_name.i18n_key}.#{attribute}"
|
59
60
|
end
|
60
61
|
end
|
61
62
|
|
62
63
|
defaults << :"attributes.#{attribute}"
|
63
|
-
defaults << options
|
64
|
-
defaults <<
|
64
|
+
defaults << options[:default] if options[:default]
|
65
|
+
defaults << MISSING_TRANSLATION
|
65
66
|
|
66
|
-
|
67
|
-
|
67
|
+
translation = I18n.translate(defaults.shift, count: 1, **options, default: defaults)
|
68
|
+
translation = attribute.humanize if translation == MISSING_TRANSLATION
|
69
|
+
translation
|
68
70
|
end
|
69
71
|
end
|
70
72
|
end
|
@@ -4,7 +4,29 @@ require "active_model/type/integer"
|
|
4
4
|
|
5
5
|
module ActiveModel
|
6
6
|
module Type
|
7
|
-
|
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
|
+
|
8
30
|
private
|
9
31
|
def max_value
|
10
32
|
::Float::INFINITY
|
@@ -2,7 +2,13 @@
|
|
2
2
|
|
3
3
|
module ActiveModel
|
4
4
|
module Type
|
5
|
-
|
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
|
6
12
|
def type
|
7
13
|
:binary
|
8
14
|
end
|
@@ -2,17 +2,15 @@
|
|
2
2
|
|
3
3
|
module ActiveModel
|
4
4
|
module Type
|
5
|
-
#
|
5
|
+
# = Active Model \Boolean \Type
|
6
6
|
#
|
7
|
-
# A class that behaves like a boolean type, including rules for coercion of
|
7
|
+
# A class that behaves like a boolean type, including rules for coercion of
|
8
|
+
# user input.
|
8
9
|
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
# - "false", "f" , "0", +0+ or any other value in +FALSE_VALUES+ will be coerced to +false+
|
14
|
-
# - Empty strings are coerced to +nil+
|
15
|
-
# - All other values will be coerced to +true+
|
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+.
|
16
14
|
class Boolean < Value
|
17
15
|
FALSE_VALUES = [
|
18
16
|
false, 0,
|
@@ -33,6 +31,10 @@ module ActiveModel
|
|
33
31
|
cast(value)
|
34
32
|
end
|
35
33
|
|
34
|
+
def serialize_cast_value(value) # :nodoc:
|
35
|
+
value
|
36
|
+
end
|
37
|
+
|
36
38
|
private
|
37
39
|
def cast_value(value)
|
38
40
|
if value == ""
|
@@ -2,7 +2,28 @@
|
|
2
2
|
|
3
3
|
module ActiveModel
|
4
4
|
module Type
|
5
|
-
|
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
|
6
27
|
include Helpers::Timezone
|
7
28
|
include Helpers::AcceptsMultiparameterTime.new
|
8
29
|
|
@@ -34,7 +55,12 @@ module ActiveModel
|
|
34
55
|
end
|
35
56
|
|
36
57
|
def fallback_string_to_date(string)
|
37
|
-
|
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
|
38
64
|
end
|
39
65
|
|
40
66
|
def new_date(year, mon, mday)
|
@@ -2,12 +2,49 @@
|
|
2
2
|
|
3
3
|
module ActiveModel
|
4
4
|
module Type
|
5
|
-
|
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
|
6
43
|
include Helpers::Timezone
|
7
|
-
include Helpers::TimeValue
|
8
44
|
include Helpers::AcceptsMultiparameterTime.new(
|
9
45
|
defaults: { 4 => 0, 5 => 0 }
|
10
46
|
)
|
47
|
+
include Helpers::TimeValue
|
11
48
|
|
12
49
|
def type
|
13
50
|
:datetime
|
@@ -28,7 +65,12 @@ module ActiveModel
|
|
28
65
|
end
|
29
66
|
|
30
67
|
def fallback_string_to_time(string)
|
31
|
-
time_hash =
|
68
|
+
time_hash = begin
|
69
|
+
::Date._parse(string)
|
70
|
+
rescue ArgumentError
|
71
|
+
end
|
72
|
+
return unless time_hash
|
73
|
+
|
32
74
|
time_hash[:sec_fraction] = microseconds(time_hash)
|
33
75
|
|
34
76
|
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
|
@@ -4,7 +4,45 @@ require "bigdecimal/util"
|
|
4
4
|
|
5
5
|
module ActiveModel
|
6
6
|
module Type
|
7
|
-
|
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
|
8
46
|
include Helpers::Numeric
|
9
47
|
BIGDECIMAL_PRECISION = 18
|
10
48
|
|
@@ -4,7 +4,36 @@ require "active_support/core_ext/object/try"
|
|
4
4
|
|
5
5
|
module ActiveModel
|
6
6
|
module Type
|
7
|
-
|
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
|
8
37
|
include Helpers::Numeric
|
9
38
|
|
10
39
|
def type
|
@@ -7,7 +7,7 @@ module ActiveModel
|
|
7
7
|
module Type
|
8
8
|
module Helpers # :nodoc: all
|
9
9
|
module TimeValue
|
10
|
-
def
|
10
|
+
def serialize_cast_value(value)
|
11
11
|
value = apply_seconds_precision(value)
|
12
12
|
|
13
13
|
if value.acts_like?(:time)
|
@@ -69,20 +69,36 @@ module ActiveModel
|
|
69
69
|
\z
|
70
70
|
/x
|
71
71
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
72
|
+
if RUBY_VERSION >= "3.2"
|
73
|
+
def fast_string_to_time(string)
|
74
|
+
return unless ISO_DATETIME.match?(string)
|
75
|
+
|
76
|
+
if is_utc?
|
77
|
+
# XXX: Wrapping the Time object with Time.at because Time.new with `in:` in Ruby 3.2.0 used to return an invalid Time object
|
78
|
+
# see: https://bugs.ruby-lang.org/issues/19292
|
79
|
+
::Time.at(::Time.new(string, in: "UTC"))
|
80
|
+
else
|
81
|
+
::Time.new(string)
|
82
|
+
end
|
83
|
+
rescue ArgumentError
|
84
|
+
nil
|
79
85
|
end
|
86
|
+
else
|
87
|
+
def fast_string_to_time(string)
|
88
|
+
return unless ISO_DATETIME =~ string
|
80
89
|
|
81
|
-
|
82
|
-
|
83
|
-
|
90
|
+
usec = $7.to_i
|
91
|
+
usec_len = $7&.length
|
92
|
+
if usec_len&.< 6
|
93
|
+
usec *= 10**(6 - usec_len)
|
94
|
+
end
|
84
95
|
|
85
|
-
|
96
|
+
if $8
|
97
|
+
offset = $8 == "Z" ? 0 : $8.to_i * 3600 + $9.to_i * 60
|
98
|
+
end
|
99
|
+
|
100
|
+
new_time($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, usec, offset)
|
101
|
+
end
|
86
102
|
end
|
87
103
|
end
|
88
104
|
end
|
@@ -2,7 +2,39 @@
|
|
2
2
|
|
3
3
|
module ActiveModel
|
4
4
|
module Type
|
5
|
-
|
5
|
+
# = Active Model \ImmutableString \Type
|
6
|
+
#
|
7
|
+
# Attribute type to represent immutable strings. It casts incoming values to
|
8
|
+
# frozen strings.
|
9
|
+
#
|
10
|
+
# class Person
|
11
|
+
# include ActiveModel::Attributes
|
12
|
+
#
|
13
|
+
# attribute :name, :immutable_string
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# person = Person.new
|
17
|
+
# person.name = 1
|
18
|
+
#
|
19
|
+
# person.name # => "1"
|
20
|
+
# person.name.frozen? # => true
|
21
|
+
#
|
22
|
+
# Values are coerced to strings using their +to_s+ method. Boolean values
|
23
|
+
# are treated differently, however: +true+ will be cast to <tt>"t"</tt> and
|
24
|
+
# +false+ will be cast to <tt>"f"</tt>. These strings can be customized when
|
25
|
+
# declaring an attribute:
|
26
|
+
#
|
27
|
+
# class Person
|
28
|
+
# include ActiveModel::Attributes
|
29
|
+
#
|
30
|
+
# attribute :active, :immutable_string, true: "aye", false: "nay"
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# person = Person.new
|
34
|
+
# person.active = true
|
35
|
+
#
|
36
|
+
# person.active # => "aye"
|
37
|
+
class ImmutableString < Value
|
6
38
|
def initialize(**args)
|
7
39
|
@true = -(args.delete(:true)&.to_s || "t")
|
8
40
|
@false = -(args.delete(:false)&.to_s || "f")
|
@@ -22,6 +54,10 @@ module ActiveModel
|
|
22
54
|
end
|
23
55
|
end
|
24
56
|
|
57
|
+
def serialize_cast_value(value) # :nodoc:
|
58
|
+
value
|
59
|
+
end
|
60
|
+
|
25
61
|
private
|
26
62
|
def cast_value(value)
|
27
63
|
case value
|