activemodel 7.0.8.1 → 7.2.2.1
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 +30 -233
- data/MIT-LICENSE +1 -1
- data/README.rdoc +18 -18
- 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 +27 -2
- data/lib/active_model/attribute_assignment.rb +4 -2
- data/lib/active_model/attribute_methods.rb +145 -85
- data/lib/active_model/attribute_registration.rb +117 -0
- data/lib/active_model/attribute_set.rb +10 -1
- data/lib/active_model/attributes.rb +78 -48
- data/lib/active_model/callbacks.rb +6 -6
- 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 +37 -6
- data/lib/active_model/forbidden_attributes_protection.rb +2 -0
- data/lib/active_model/gem_version.rb +3 -3
- data/lib/active_model/lint.rb +1 -1
- data/lib/active_model/locale/en.yml +1 -0
- data/lib/active_model/model.rb +34 -2
- data/lib/active_model/naming.rb +29 -10
- data/lib/active_model/railtie.rb +4 -0
- data/lib/active_model/secure_password.rb +62 -24
- 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 +6 -1
- data/lib/active_model/type/helpers/time_value.rb +50 -13
- data/lib/active_model/type/helpers/timezone.rb +5 -1
- data/lib/active_model/type/immutable_string.rb +37 -1
- data/lib/active_model/type/integer.rb +44 -1
- data/lib/active_model/type/registry.rb +2 -3
- 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 +5 -5
- 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 +16 -8
- 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 +1 -1
- 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 +44 -9
- data/lib/active_model/validator.rb +7 -5
- data/lib/active_model/version.rb +1 -1
- data/lib/active_model.rb +5 -1
- metadata +17 -12
@@ -18,6 +18,7 @@ en:
|
|
18
18
|
too_long:
|
19
19
|
one: "is too long (maximum is 1 character)"
|
20
20
|
other: "is too long (maximum is %{count} characters)"
|
21
|
+
password_too_long: "is too long"
|
21
22
|
too_short:
|
22
23
|
one: "is too short (minimum is 1 character)"
|
23
24
|
other: "is too short (minimum is %{count} characters)"
|
data/lib/active_model/model.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveModel
|
4
|
-
#
|
4
|
+
# = Active \Model \Basic \Model
|
5
5
|
#
|
6
6
|
# Allows implementing models similar to ActiveRecord::Base.
|
7
7
|
# Includes ActiveModel::API for the required interface for an
|
@@ -37,10 +37,42 @@ module ActiveModel
|
|
37
37
|
# person.omg # => true
|
38
38
|
#
|
39
39
|
# For more detailed information on other functionalities available, please
|
40
|
-
# refer to the specific modules included in
|
40
|
+
# refer to the specific modules included in +ActiveModel::Model+
|
41
41
|
# (see below).
|
42
42
|
module Model
|
43
43
|
extend ActiveSupport::Concern
|
44
44
|
include ActiveModel::API
|
45
|
+
include ActiveModel::Access
|
46
|
+
|
47
|
+
##
|
48
|
+
# :method: slice
|
49
|
+
#
|
50
|
+
# :call-seq: slice(*methods)
|
51
|
+
#
|
52
|
+
# Returns a hash of the given methods with their names as keys and returned
|
53
|
+
# values as values.
|
54
|
+
#
|
55
|
+
# person = Person.new(id: 1, name: "bob")
|
56
|
+
# person.slice(:id, :name)
|
57
|
+
# => { "id" => 1, "name" => "bob" }
|
58
|
+
#
|
59
|
+
#--
|
60
|
+
# Implemented by ActiveModel::Access#slice.
|
61
|
+
|
62
|
+
##
|
63
|
+
# :method: values_at
|
64
|
+
#
|
65
|
+
# :call-seq: values_at(*methods)
|
66
|
+
#
|
67
|
+
# Returns an array of the values returned by the given methods.
|
68
|
+
#
|
69
|
+
# person = Person.new(id: 1, name: "bob")
|
70
|
+
# person.values_at(:id, :name)
|
71
|
+
# => [1, "bob"]
|
72
|
+
#
|
73
|
+
#--
|
74
|
+
# Implemented by ActiveModel::Access#values_at.
|
45
75
|
end
|
76
|
+
|
77
|
+
ActiveSupport.run_load_hooks(:active_model, Model)
|
46
78
|
end
|
data/lib/active_model/naming.rb
CHANGED
@@ -195,18 +195,15 @@ module ActiveModel
|
|
195
195
|
#
|
196
196
|
# Specify +options+ with additional translating options.
|
197
197
|
def human(options = {})
|
198
|
-
return @human
|
199
|
-
@klass.respond_to?(:i18n_scope)
|
200
|
-
|
201
|
-
defaults = @klass.lookup_ancestors.map do |klass|
|
202
|
-
klass.model_name.i18n_key
|
203
|
-
end
|
198
|
+
return @human if i18n_keys.empty? || i18n_scope.empty?
|
204
199
|
|
200
|
+
key, *defaults = i18n_keys
|
205
201
|
defaults << options[:default] if options[:default]
|
206
|
-
defaults <<
|
202
|
+
defaults << MISSING_TRANSLATION
|
207
203
|
|
208
|
-
|
209
|
-
|
204
|
+
translation = I18n.translate(key, scope: i18n_scope, count: 1, **options, default: defaults)
|
205
|
+
translation = @human if translation == MISSING_TRANSLATION
|
206
|
+
translation
|
210
207
|
end
|
211
208
|
|
212
209
|
def uncountable?
|
@@ -214,12 +211,26 @@ module ActiveModel
|
|
214
211
|
end
|
215
212
|
|
216
213
|
private
|
214
|
+
MISSING_TRANSLATION = -(2**60) # :nodoc:
|
215
|
+
|
217
216
|
def _singularize(string)
|
218
217
|
ActiveSupport::Inflector.underscore(string).tr("/", "_")
|
219
218
|
end
|
219
|
+
|
220
|
+
def i18n_keys
|
221
|
+
@i18n_keys ||= if @klass.respond_to?(:lookup_ancestors)
|
222
|
+
@klass.lookup_ancestors.map { |klass| klass.model_name.i18n_key }
|
223
|
+
else
|
224
|
+
[]
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def i18n_scope
|
229
|
+
@i18n_scope ||= @klass.respond_to?(:i18n_scope) ? [@klass.i18n_scope, :models] : []
|
230
|
+
end
|
220
231
|
end
|
221
232
|
|
222
|
-
#
|
233
|
+
# = Active \Model \Naming
|
223
234
|
#
|
224
235
|
# Creates a +model_name+ method on your object.
|
225
236
|
#
|
@@ -336,5 +347,13 @@ module ActiveModel
|
|
336
347
|
end
|
337
348
|
end
|
338
349
|
private_class_method :model_name_from_record_or_class
|
350
|
+
|
351
|
+
private
|
352
|
+
def inherited(base)
|
353
|
+
super
|
354
|
+
base.class_eval do
|
355
|
+
@_model_name = nil
|
356
|
+
end
|
357
|
+
end
|
339
358
|
end
|
340
359
|
end
|
data/lib/active_model/railtie.rb
CHANGED
@@ -9,6 +9,10 @@ module ActiveModel
|
|
9
9
|
|
10
10
|
config.active_model = ActiveSupport::OrderedOptions.new
|
11
11
|
|
12
|
+
initializer "active_model.deprecator", before: :load_environment_config do |app|
|
13
|
+
app.deprecators[:active_model] = ActiveModel.deprecator
|
14
|
+
end
|
15
|
+
|
12
16
|
initializer "active_model.secure_password" do
|
13
17
|
ActiveModel::SecurePassword.min_cost = Rails.env.test?
|
14
18
|
end
|
@@ -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,12 +29,19 @@ 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.
|
36
41
|
#
|
37
|
-
#
|
42
|
+
# To use +has_secure_password+, add bcrypt (~> 3.1.7) to your Gemfile:
|
43
|
+
#
|
44
|
+
# gem "bcrypt", "~> 3.1.7"
|
38
45
|
#
|
39
46
|
# ==== Examples
|
40
47
|
#
|
@@ -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
|