activemodel 7.2.2.1 → 8.1.2
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 +25 -45
- data/README.rdoc +1 -1
- data/lib/active_model/attribute/user_provided_default.rb +10 -0
- data/lib/active_model/attribute.rb +9 -1
- data/lib/active_model/attribute_assignment.rb +23 -2
- data/lib/active_model/attribute_methods.rb +5 -5
- data/lib/active_model/attribute_mutation_tracker.rb +5 -5
- data/lib/active_model/attribute_set.rb +1 -1
- data/lib/active_model/attributes/normalization.rb +192 -0
- data/lib/active_model/conversion.rb +1 -1
- data/lib/active_model/dirty.rb +16 -9
- data/lib/active_model/error.rb +3 -2
- data/lib/active_model/errors.rb +1 -4
- data/lib/active_model/gem_version.rb +3 -3
- data/lib/active_model/lint.rb +7 -3
- data/lib/active_model/model.rb +2 -2
- data/lib/active_model/naming.rb +0 -1
- data/lib/active_model/nested_error.rb +1 -3
- data/lib/active_model/railtie.rb +8 -4
- data/lib/active_model/secure_password.rb +61 -4
- data/lib/active_model/serialization.rb +21 -21
- data/lib/active_model/translation.rb +22 -5
- data/lib/active_model/type/big_integer.rb +21 -0
- data/lib/active_model/type/boolean.rb +1 -0
- data/lib/active_model/type/date.rb +1 -0
- data/lib/active_model/type/date_time.rb +8 -0
- data/lib/active_model/type/decimal.rb +1 -0
- data/lib/active_model/type/float.rb +9 -8
- data/lib/active_model/type/helpers/immutable.rb +13 -0
- data/lib/active_model/type/helpers/time_value.rb +20 -44
- data/lib/active_model/type/helpers.rb +1 -0
- data/lib/active_model/type/immutable_string.rb +2 -0
- data/lib/active_model/type/integer.rb +31 -19
- data/lib/active_model/type/string.rb +4 -0
- data/lib/active_model/type/value.rb +4 -4
- data/lib/active_model/validations/acceptance.rb +1 -1
- data/lib/active_model/validations/callbacks.rb +10 -0
- data/lib/active_model/validations/validates.rb +7 -2
- data/lib/active_model/validations.rb +57 -32
- data/lib/active_model.rb +6 -0
- metadata +11 -12
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "active_model/error"
|
|
4
|
-
require "forwardable"
|
|
5
4
|
|
|
6
5
|
module ActiveModel
|
|
7
6
|
class NestedError < Error
|
|
@@ -16,7 +15,6 @@ module ActiveModel
|
|
|
16
15
|
|
|
17
16
|
attr_reader :inner_error
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
def_delegators :@inner_error, :message
|
|
18
|
+
delegate :message, to: :@inner_error
|
|
21
19
|
end
|
|
22
20
|
end
|
data/lib/active_model/railtie.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "active_model"
|
|
4
3
|
require "rails"
|
|
4
|
+
require "active_model"
|
|
5
5
|
|
|
6
6
|
module ActiveModel
|
|
7
7
|
class Railtie < Rails::Railtie # :nodoc:
|
|
@@ -14,11 +14,15 @@ module ActiveModel
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
initializer "active_model.secure_password" do
|
|
17
|
-
|
|
17
|
+
ActiveSupport.on_load(:active_model_secure_password) do
|
|
18
|
+
ActiveModel::SecurePassword.min_cost = Rails.env.test?
|
|
19
|
+
end
|
|
18
20
|
end
|
|
19
21
|
|
|
20
|
-
initializer "active_model.i18n_customize_full_message" do
|
|
21
|
-
|
|
22
|
+
initializer "active_model.i18n_customize_full_message" do |app|
|
|
23
|
+
ActiveSupport.on_load(:active_model_error) do
|
|
24
|
+
ActiveModel::Error.i18n_customize_full_message = app.config.active_model.i18n_customize_full_message || false
|
|
25
|
+
end
|
|
22
26
|
end
|
|
23
27
|
end
|
|
24
28
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "active_support/core_ext/numeric/time"
|
|
4
|
+
|
|
3
5
|
module ActiveModel
|
|
4
6
|
module SecurePassword
|
|
5
7
|
extend ActiveSupport::Concern
|
|
@@ -9,6 +11,8 @@ module ActiveModel
|
|
|
9
11
|
# Hence need to put a restriction on password length.
|
|
10
12
|
MAX_PASSWORD_LENGTH_ALLOWED = 72
|
|
11
13
|
|
|
14
|
+
DEFAULT_RESET_TOKEN_EXPIRES_IN = 15.minutes
|
|
15
|
+
|
|
12
16
|
class << self
|
|
13
17
|
attr_accessor :min_cost # :nodoc:
|
|
14
18
|
end
|
|
@@ -39,6 +43,15 @@ module ActiveModel
|
|
|
39
43
|
# <tt>validations: false</tt> as an argument. This allows complete
|
|
40
44
|
# customizability of validation behavior.
|
|
41
45
|
#
|
|
46
|
+
# A password reset token (valid for 15 minutes by default) is automatically
|
|
47
|
+
# configured when +reset_token+ is set to true (which it is by default)
|
|
48
|
+
# and the object responds to +generates_token_for+ (which Active Records do).
|
|
49
|
+
#
|
|
50
|
+
# Finally, the reset token expiry can be customized by passing a hash to
|
|
51
|
+
# +has_secure_password+:
|
|
52
|
+
#
|
|
53
|
+
# has_secure_password reset_token: { expires_in: 1.hour }
|
|
54
|
+
#
|
|
42
55
|
# To use +has_secure_password+, add bcrypt (~> 3.1.7) to your Gemfile:
|
|
43
56
|
#
|
|
44
57
|
# gem "bcrypt", "~> 3.1.7"
|
|
@@ -98,7 +111,18 @@ module ActiveModel
|
|
|
98
111
|
# account.is_guest = true
|
|
99
112
|
# account.valid? # => true
|
|
100
113
|
#
|
|
101
|
-
|
|
114
|
+
# ===== Using the password reset token
|
|
115
|
+
#
|
|
116
|
+
# user = User.create!(name: "david", password: "123", password_confirmation: "123")
|
|
117
|
+
# token = user.password_reset_token
|
|
118
|
+
# User.find_by_password_reset_token(token) # returns user
|
|
119
|
+
#
|
|
120
|
+
# # 16 minutes later...
|
|
121
|
+
# User.find_by_password_reset_token(token) # returns nil
|
|
122
|
+
#
|
|
123
|
+
# # raises ActiveSupport::MessageVerifier::InvalidSignature since the token is expired
|
|
124
|
+
# User.find_by_password_reset_token!(token)
|
|
125
|
+
def has_secure_password(attribute = :password, validations: true, reset_token: true)
|
|
102
126
|
# Load bcrypt gem only when has_secure_password is used.
|
|
103
127
|
# This is to avoid ActiveModel (and by extension the entire framework)
|
|
104
128
|
# being dependent on a binary library.
|
|
@@ -109,7 +133,7 @@ module ActiveModel
|
|
|
109
133
|
raise
|
|
110
134
|
end
|
|
111
135
|
|
|
112
|
-
include InstanceMethodsOnActivation.new(attribute)
|
|
136
|
+
include InstanceMethodsOnActivation.new(attribute, reset_token: reset_token)
|
|
113
137
|
|
|
114
138
|
if validations
|
|
115
139
|
include ActiveModel::Validations
|
|
@@ -140,13 +164,37 @@ module ActiveModel
|
|
|
140
164
|
end
|
|
141
165
|
end
|
|
142
166
|
|
|
143
|
-
validates_confirmation_of attribute,
|
|
167
|
+
validates_confirmation_of attribute, allow_nil: true
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Only generate tokens for records that are capable of doing so (Active Records, not vanilla Active Models)
|
|
171
|
+
if reset_token && respond_to?(:generates_token_for)
|
|
172
|
+
reset_token_expires_in = reset_token.is_a?(Hash) ? reset_token[:expires_in] : DEFAULT_RESET_TOKEN_EXPIRES_IN
|
|
173
|
+
|
|
174
|
+
silence_redefinition_of_method(:"#{attribute}_reset_token_expires_in")
|
|
175
|
+
define_method(:"#{attribute}_reset_token_expires_in") { reset_token_expires_in }
|
|
176
|
+
|
|
177
|
+
generates_token_for :"#{attribute}_reset", expires_in: reset_token_expires_in do
|
|
178
|
+
public_send(:"#{attribute}_salt")&.last(10)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
182
|
+
silence_redefinition_of_method :find_by_#{attribute}_reset_token
|
|
183
|
+
def self.find_by_#{attribute}_reset_token(token)
|
|
184
|
+
find_by_token_for(:#{attribute}_reset, token)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
silence_redefinition_of_method :find_by_#{attribute}_reset_token!
|
|
188
|
+
def self.find_by_#{attribute}_reset_token!(token)
|
|
189
|
+
find_by_token_for!(:#{attribute}_reset, token)
|
|
190
|
+
end
|
|
191
|
+
RUBY
|
|
144
192
|
end
|
|
145
193
|
end
|
|
146
194
|
end
|
|
147
195
|
|
|
148
196
|
class InstanceMethodsOnActivation < Module
|
|
149
|
-
def initialize(attribute)
|
|
197
|
+
def initialize(attribute, reset_token:)
|
|
150
198
|
attr_reader attribute
|
|
151
199
|
|
|
152
200
|
define_method("#{attribute}=") do |unencrypted_password|
|
|
@@ -184,7 +232,16 @@ module ActiveModel
|
|
|
184
232
|
end
|
|
185
233
|
|
|
186
234
|
alias_method :authenticate, :authenticate_password if attribute == :password
|
|
235
|
+
|
|
236
|
+
if reset_token
|
|
237
|
+
# Returns the class-level configured reset token for the password.
|
|
238
|
+
define_method("#{attribute}_reset_token") do
|
|
239
|
+
generate_token_for(:"#{attribute}_reset")
|
|
240
|
+
end
|
|
241
|
+
end
|
|
187
242
|
end
|
|
188
243
|
end
|
|
189
244
|
end
|
|
245
|
+
|
|
246
|
+
ActiveSupport.run_load_hooks(:active_model_secure_password, SecurePassword)
|
|
190
247
|
end
|
|
@@ -29,8 +29,8 @@ module ActiveModel
|
|
|
29
29
|
# An +attributes+ hash must be defined and should contain any attributes you
|
|
30
30
|
# need to be serialized. Attributes must be strings, not symbols.
|
|
31
31
|
# When called, serializable hash will use instance methods that match the name
|
|
32
|
-
# of the attributes hash's keys. In order to override this behavior,
|
|
33
|
-
#
|
|
32
|
+
# of the attributes hash's keys. In order to override this behavior, override
|
|
33
|
+
# the +read_attribute_for_serialization+ method.
|
|
34
34
|
#
|
|
35
35
|
# ActiveModel::Serializers::JSON module automatically includes
|
|
36
36
|
# the +ActiveModel::Serialization+ module, so there is no need to
|
|
@@ -128,7 +128,7 @@ module ActiveModel
|
|
|
128
128
|
return serializable_attributes(attribute_names) if options.blank?
|
|
129
129
|
|
|
130
130
|
if only = options[:only]
|
|
131
|
-
attribute_names
|
|
131
|
+
attribute_names = Array(only).map(&:to_s) & attribute_names
|
|
132
132
|
elsif except = options[:except]
|
|
133
133
|
attribute_names -= Array(except).map(&:to_s)
|
|
134
134
|
end
|
|
@@ -148,29 +148,29 @@ module ActiveModel
|
|
|
148
148
|
hash
|
|
149
149
|
end
|
|
150
150
|
|
|
151
|
+
# Hook method defining how an attribute value should be retrieved for
|
|
152
|
+
# serialization. By default this is assumed to be an instance named after
|
|
153
|
+
# the attribute. Override this method in subclasses should you need to
|
|
154
|
+
# retrieve the value for a given attribute differently:
|
|
155
|
+
#
|
|
156
|
+
# class MyClass
|
|
157
|
+
# include ActiveModel::Serialization
|
|
158
|
+
#
|
|
159
|
+
# def initialize(data = {})
|
|
160
|
+
# @data = data
|
|
161
|
+
# end
|
|
162
|
+
#
|
|
163
|
+
# def read_attribute_for_serialization(key)
|
|
164
|
+
# @data[key]
|
|
165
|
+
# end
|
|
166
|
+
# end
|
|
167
|
+
alias :read_attribute_for_serialization :send
|
|
168
|
+
|
|
151
169
|
private
|
|
152
170
|
def attribute_names_for_serialization
|
|
153
171
|
attributes.keys
|
|
154
172
|
end
|
|
155
173
|
|
|
156
|
-
# Hook method defining how an attribute value should be retrieved for
|
|
157
|
-
# serialization. By default this is assumed to be an instance named after
|
|
158
|
-
# the attribute. Override this method in subclasses should you need to
|
|
159
|
-
# retrieve the value for a given attribute differently:
|
|
160
|
-
#
|
|
161
|
-
# class MyClass
|
|
162
|
-
# include ActiveModel::Serialization
|
|
163
|
-
#
|
|
164
|
-
# def initialize(data = {})
|
|
165
|
-
# @data = data
|
|
166
|
-
# end
|
|
167
|
-
#
|
|
168
|
-
# def read_attribute_for_serialization(key)
|
|
169
|
-
# @data[key]
|
|
170
|
-
# end
|
|
171
|
-
# end
|
|
172
|
-
alias :read_attribute_for_serialization :send
|
|
173
|
-
|
|
174
174
|
def serializable_attributes(attribute_names)
|
|
175
175
|
attribute_names.index_with { |n| read_attribute_for_serialization(n) }
|
|
176
176
|
end
|
|
@@ -22,6 +22,8 @@ module ActiveModel
|
|
|
22
22
|
module Translation
|
|
23
23
|
include ActiveModel::Naming
|
|
24
24
|
|
|
25
|
+
singleton_class.attr_accessor :raise_on_missing_translations
|
|
26
|
+
|
|
25
27
|
# Returns the +i18n_scope+ for the class. Override if you want custom lookup.
|
|
26
28
|
def i18n_scope
|
|
27
29
|
:activemodel
|
|
@@ -50,23 +52,38 @@ module ActiveModel
|
|
|
50
52
|
namespace, _, attribute = attribute.rpartition(".")
|
|
51
53
|
namespace.tr!(".", "/")
|
|
52
54
|
|
|
55
|
+
if attribute.present?
|
|
56
|
+
key = "#{namespace}.#{attribute}"
|
|
57
|
+
separator = "/"
|
|
58
|
+
else
|
|
59
|
+
key = namespace
|
|
60
|
+
separator = "."
|
|
61
|
+
end
|
|
62
|
+
|
|
53
63
|
defaults = lookup_ancestors.map do |klass|
|
|
54
|
-
:"#{i18n_scope}.attributes.#{klass.model_name.i18n_key}
|
|
64
|
+
:"#{i18n_scope}.attributes.#{klass.model_name.i18n_key}#{separator}#{key}"
|
|
55
65
|
end
|
|
56
|
-
defaults << :"#{i18n_scope}.attributes.#{
|
|
66
|
+
defaults << :"#{i18n_scope}.attributes.#{key}"
|
|
67
|
+
defaults << :"attributes.#{key}"
|
|
57
68
|
else
|
|
58
69
|
defaults = lookup_ancestors.map do |klass|
|
|
59
70
|
:"#{i18n_scope}.attributes.#{klass.model_name.i18n_key}.#{attribute}"
|
|
60
71
|
end
|
|
61
72
|
end
|
|
62
73
|
|
|
74
|
+
raise_on_missing = options.fetch(:raise, Translation.raise_on_missing_translations)
|
|
75
|
+
|
|
63
76
|
defaults << :"attributes.#{attribute}"
|
|
64
77
|
defaults << options[:default] if options[:default]
|
|
65
|
-
defaults << MISSING_TRANSLATION
|
|
78
|
+
defaults << MISSING_TRANSLATION unless raise_on_missing
|
|
66
79
|
|
|
67
|
-
translation = I18n.translate(defaults.shift, count: 1, **options, default: defaults)
|
|
68
|
-
|
|
80
|
+
translation = I18n.translate(defaults.shift, count: 1, raise: raise_on_missing, **options, default: defaults)
|
|
81
|
+
if translation == MISSING_TRANSLATION
|
|
82
|
+
translation = attribute.present? ? attribute.humanize : namespace.humanize
|
|
83
|
+
end
|
|
69
84
|
translation
|
|
70
85
|
end
|
|
71
86
|
end
|
|
87
|
+
|
|
88
|
+
ActiveSupport.run_load_hooks(:active_model_translation, Translation)
|
|
72
89
|
end
|
|
@@ -23,10 +23,31 @@ module ActiveModel
|
|
|
23
23
|
# All casting and serialization are performed in the same way as the
|
|
24
24
|
# standard ActiveModel::Type::Integer type.
|
|
25
25
|
class BigInteger < Integer
|
|
26
|
+
def serialize(value) # :nodoc:
|
|
27
|
+
case value
|
|
28
|
+
when ::Integer
|
|
29
|
+
# noop
|
|
30
|
+
when ::String
|
|
31
|
+
int = value.to_i
|
|
32
|
+
if int.zero? && value != "0"
|
|
33
|
+
return if non_numeric_string?(value)
|
|
34
|
+
end
|
|
35
|
+
value = int
|
|
36
|
+
else
|
|
37
|
+
value = super
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
value
|
|
41
|
+
end
|
|
42
|
+
|
|
26
43
|
def serialize_cast_value(value) # :nodoc:
|
|
27
44
|
value
|
|
28
45
|
end
|
|
29
46
|
|
|
47
|
+
def serializable?(value, &)
|
|
48
|
+
true
|
|
49
|
+
end
|
|
50
|
+
|
|
30
51
|
private
|
|
31
52
|
def max_value
|
|
32
53
|
::Float::INFINITY
|
|
@@ -24,6 +24,7 @@ module ActiveModel
|
|
|
24
24
|
# String values are parsed using the ISO 8601 date format. Any other values
|
|
25
25
|
# are cast using their +to_date+ method, if it exists.
|
|
26
26
|
class Date < Value
|
|
27
|
+
include Helpers::Immutable
|
|
27
28
|
include Helpers::Timezone
|
|
28
29
|
include Helpers::AcceptsMultiparameterTime.new
|
|
29
30
|
|
|
@@ -50,6 +50,14 @@ module ActiveModel
|
|
|
50
50
|
:datetime
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
def mutable? # :nodoc:
|
|
54
|
+
# Time#zone can be mutated by #utc or #localtime
|
|
55
|
+
# However when serializing the time zone will always
|
|
56
|
+
# be coerced and even if the zone was mutated Time instances
|
|
57
|
+
# remain equal, so we don't need to implement `#changed_in_place?`
|
|
58
|
+
true
|
|
59
|
+
end
|
|
60
|
+
|
|
53
61
|
private
|
|
54
62
|
def cast_value(value)
|
|
55
63
|
return apply_seconds_precision(value) unless value.is_a?(::String)
|
|
@@ -15,14 +15,6 @@ module ActiveModel
|
|
|
15
15
|
# attribute :weight, :float
|
|
16
16
|
# end
|
|
17
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
18
|
# bag = BagOfCoffee.new
|
|
27
19
|
#
|
|
28
20
|
# bag.weight = "0.25"
|
|
@@ -33,7 +25,16 @@ module ActiveModel
|
|
|
33
25
|
#
|
|
34
26
|
# bag.weight = "NaN"
|
|
35
27
|
# bag.weight # => Float::NAN
|
|
28
|
+
#
|
|
29
|
+
# Values are cast using their +to_f+ method, except for the following
|
|
30
|
+
# strings:
|
|
31
|
+
#
|
|
32
|
+
# - Blank strings are cast to +nil+.
|
|
33
|
+
# - <tt>"Infinity"</tt> is cast to +Float::INFINITY+.
|
|
34
|
+
# - <tt>"-Infinity"</tt> is cast to <tt>-Float::INFINITY</tt>.
|
|
35
|
+
# - <tt>"NaN"</tt> is cast to +Float::NAN+.
|
|
36
36
|
class Float < Value
|
|
37
|
+
include Helpers::Immutable
|
|
37
38
|
include Helpers::Numeric
|
|
38
39
|
|
|
39
40
|
def type
|
|
@@ -69,56 +69,32 @@ module ActiveModel
|
|
|
69
69
|
\z
|
|
70
70
|
/x
|
|
71
71
|
|
|
72
|
-
if
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
::Time.new(string)
|
|
84
|
-
end
|
|
85
|
-
rescue ArgumentError
|
|
86
|
-
nil
|
|
87
|
-
end
|
|
88
|
-
else
|
|
89
|
-
def fast_string_to_time(string)
|
|
90
|
-
return unless string.include?("-") # Time.new("1234") # => 1234-01-01 00:00:00
|
|
91
|
-
|
|
92
|
-
if is_utc?
|
|
93
|
-
::Time.new(string, in: "UTC")
|
|
94
|
-
else
|
|
95
|
-
::Time.new(string)
|
|
96
|
-
end
|
|
97
|
-
rescue ArgumentError
|
|
98
|
-
nil
|
|
72
|
+
if Time.new(2000, 1, 1, 0, 0, 0, "-00:00").yday != 1 # Early 3.2.x had a bug
|
|
73
|
+
# BUG: Wrapping the Time object with Time.at because Time.new with `in:` in Ruby 3.2.0
|
|
74
|
+
# used to return an invalid Time object
|
|
75
|
+
# see: https://bugs.ruby-lang.org/issues/19292
|
|
76
|
+
def fast_string_to_time(string)
|
|
77
|
+
return unless string.include?("-") # Time.new("1234") # => 1234-01-01 00:00:00
|
|
78
|
+
|
|
79
|
+
if is_utc?
|
|
80
|
+
::Time.at(::Time.new(string, in: "UTC"))
|
|
81
|
+
else
|
|
82
|
+
::Time.new(string)
|
|
99
83
|
end
|
|
84
|
+
rescue ArgumentError
|
|
85
|
+
nil
|
|
100
86
|
end
|
|
101
87
|
else
|
|
102
88
|
def fast_string_to_time(string)
|
|
103
|
-
return unless
|
|
89
|
+
return unless string.include?("-") # Time.new("1234") # => 1234-01-01 00:00:00
|
|
104
90
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
91
|
+
if is_utc?
|
|
92
|
+
::Time.new(string, in: "UTC")
|
|
93
|
+
else
|
|
94
|
+
::Time.new(string)
|
|
109
95
|
end
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
offset = \
|
|
113
|
-
if $8 == "Z"
|
|
114
|
-
0
|
|
115
|
-
else
|
|
116
|
-
offset_h, offset_m = $8.to_i, $9.to_i
|
|
117
|
-
offset_h.to_i * 3600 + (offset_h.negative? ? -1 : 1) * offset_m * 60
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
new_time($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, usec, offset)
|
|
96
|
+
rescue ArgumentError
|
|
97
|
+
nil
|
|
122
98
|
end
|
|
123
99
|
end
|
|
124
100
|
end
|
|
@@ -3,5 +3,6 @@
|
|
|
3
3
|
require "active_model/type/helpers/accepts_multiparameter_time"
|
|
4
4
|
require "active_model/type/helpers/numeric"
|
|
5
5
|
require "active_model/type/helpers/mutable"
|
|
6
|
+
require "active_model/type/helpers/immutable"
|
|
6
7
|
require "active_model/type/helpers/time_value"
|
|
7
8
|
require "active_model/type/helpers/timezone"
|
|
@@ -42,6 +42,7 @@ module ActiveModel
|
|
|
42
42
|
# attribute :age, :integer, limit: 6
|
|
43
43
|
# end
|
|
44
44
|
class Integer < Value
|
|
45
|
+
include Helpers::Immutable
|
|
45
46
|
include Helpers::Numeric
|
|
46
47
|
|
|
47
48
|
# Column storage size in bytes.
|
|
@@ -50,7 +51,8 @@ module ActiveModel
|
|
|
50
51
|
|
|
51
52
|
def initialize(**)
|
|
52
53
|
super
|
|
53
|
-
@
|
|
54
|
+
@max = max_value
|
|
55
|
+
@min = min_value
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
def type
|
|
@@ -63,40 +65,50 @@ module ActiveModel
|
|
|
63
65
|
end
|
|
64
66
|
|
|
65
67
|
def serialize(value)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
case value
|
|
69
|
+
when ::Integer
|
|
70
|
+
# noop
|
|
71
|
+
when ::String
|
|
72
|
+
int = value.to_i
|
|
73
|
+
if int.zero? && value != "0"
|
|
74
|
+
return if non_numeric_string?(value)
|
|
75
|
+
end
|
|
76
|
+
value = int
|
|
77
|
+
else
|
|
78
|
+
value = super
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if out_of_range?(value)
|
|
82
|
+
raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit} bytes"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
value
|
|
68
86
|
end
|
|
69
87
|
|
|
70
88
|
def serialize_cast_value(value) # :nodoc:
|
|
71
|
-
|
|
89
|
+
if out_of_range?(value)
|
|
90
|
+
raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit} bytes"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
value
|
|
72
94
|
end
|
|
73
95
|
|
|
74
96
|
def serializable?(value)
|
|
75
97
|
cast_value = cast(value)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
end
|
|
98
|
+
return true unless out_of_range?(cast_value)
|
|
99
|
+
yield cast_value if block_given?
|
|
100
|
+
false
|
|
80
101
|
end
|
|
81
102
|
|
|
82
103
|
private
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def in_range?(value)
|
|
86
|
-
!value || range.member?(value)
|
|
104
|
+
def out_of_range?(value)
|
|
105
|
+
value && (@max <= value || @min > value)
|
|
87
106
|
end
|
|
88
107
|
|
|
89
108
|
def cast_value(value)
|
|
90
109
|
value.to_i rescue nil
|
|
91
110
|
end
|
|
92
111
|
|
|
93
|
-
def ensure_in_range(value)
|
|
94
|
-
unless in_range?(value)
|
|
95
|
-
raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit} bytes"
|
|
96
|
-
end
|
|
97
|
-
value
|
|
98
|
-
end
|
|
99
|
-
|
|
100
112
|
def max_value
|
|
101
113
|
1 << (_limit * 8 - 1) # 8 bits per byte with one bit for sign
|
|
102
114
|
end
|
|
@@ -25,7 +25,7 @@ module ActiveModel
|
|
|
25
25
|
# by the database. For example a boolean type can return +true+ if the
|
|
26
26
|
# value parameter is a Ruby boolean, but may return +false+ if the value
|
|
27
27
|
# parameter is some other object.
|
|
28
|
-
def serializable?(value)
|
|
28
|
+
def serializable?(value, &)
|
|
29
29
|
true
|
|
30
30
|
end
|
|
31
31
|
|
|
@@ -114,8 +114,8 @@ module ActiveModel
|
|
|
114
114
|
false
|
|
115
115
|
end
|
|
116
116
|
|
|
117
|
-
def map(value) # :nodoc:
|
|
118
|
-
|
|
117
|
+
def map(value, &) # :nodoc:
|
|
118
|
+
value
|
|
119
119
|
end
|
|
120
120
|
|
|
121
121
|
def ==(other)
|
|
@@ -138,7 +138,7 @@ module ActiveModel
|
|
|
138
138
|
end
|
|
139
139
|
|
|
140
140
|
def mutable? # :nodoc:
|
|
141
|
-
|
|
141
|
+
true
|
|
142
142
|
end
|
|
143
143
|
|
|
144
144
|
def as_json(*)
|
|
@@ -106,6 +106,16 @@ module ActiveModel
|
|
|
106
106
|
*options[:if]
|
|
107
107
|
]
|
|
108
108
|
end
|
|
109
|
+
|
|
110
|
+
if options.key?(:except_on)
|
|
111
|
+
options[:except_on] = Array(options[:except_on])
|
|
112
|
+
options[:unless] = [
|
|
113
|
+
->(o) {
|
|
114
|
+
options[:except_on].intersect?(Array(o.validation_context))
|
|
115
|
+
},
|
|
116
|
+
*options[:unless]
|
|
117
|
+
]
|
|
118
|
+
end
|
|
109
119
|
end
|
|
110
120
|
end
|
|
111
121
|
|