activemodel 5.2.7.1 → 6.1.4.6
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 +65 -111
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -4
- data/lib/active_model/attribute/user_provided_default.rb +1 -2
- data/lib/active_model/attribute.rb +21 -21
- data/lib/active_model/attribute_assignment.rb +4 -6
- data/lib/active_model/attribute_methods.rb +117 -40
- data/lib/active_model/attribute_mutation_tracker.rb +90 -33
- data/lib/active_model/attribute_set/builder.rb +81 -16
- data/lib/active_model/attribute_set/yaml_encoder.rb +1 -2
- data/lib/active_model/attribute_set.rb +20 -28
- data/lib/active_model/attributes.rb +65 -44
- data/lib/active_model/callbacks.rb +11 -9
- data/lib/active_model/conversion.rb +1 -1
- data/lib/active_model/dirty.rb +51 -101
- data/lib/active_model/error.rb +207 -0
- data/lib/active_model/errors.rb +347 -155
- data/lib/active_model/gem_version.rb +4 -4
- data/lib/active_model/lint.rb +1 -1
- data/lib/active_model/naming.rb +22 -7
- data/lib/active_model/nested_error.rb +22 -0
- data/lib/active_model/railtie.rb +6 -0
- data/lib/active_model/secure_password.rb +54 -55
- data/lib/active_model/serialization.rb +9 -7
- data/lib/active_model/serializers/json.rb +17 -9
- data/lib/active_model/translation.rb +1 -1
- data/lib/active_model/type/big_integer.rb +0 -1
- data/lib/active_model/type/binary.rb +1 -1
- data/lib/active_model/type/boolean.rb +0 -1
- data/lib/active_model/type/date.rb +0 -5
- data/lib/active_model/type/date_time.rb +3 -8
- data/lib/active_model/type/decimal.rb +0 -1
- data/lib/active_model/type/float.rb +2 -3
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +14 -6
- data/lib/active_model/type/helpers/numeric.rb +17 -6
- data/lib/active_model/type/helpers/time_value.rb +37 -15
- data/lib/active_model/type/helpers/timezone.rb +1 -1
- data/lib/active_model/type/immutable_string.rb +14 -11
- data/lib/active_model/type/integer.rb +15 -18
- data/lib/active_model/type/registry.rb +16 -16
- data/lib/active_model/type/string.rb +12 -3
- data/lib/active_model/type/time.rb +1 -6
- data/lib/active_model/type/value.rb +9 -2
- data/lib/active_model/validations/absence.rb +2 -2
- data/lib/active_model/validations/acceptance.rb +34 -27
- data/lib/active_model/validations/callbacks.rb +15 -16
- data/lib/active_model/validations/clusivity.rb +6 -3
- data/lib/active_model/validations/confirmation.rb +4 -4
- data/lib/active_model/validations/exclusion.rb +1 -1
- data/lib/active_model/validations/format.rb +2 -3
- data/lib/active_model/validations/inclusion.rb +2 -2
- data/lib/active_model/validations/length.rb +3 -3
- data/lib/active_model/validations/numericality.rb +58 -44
- data/lib/active_model/validations/presence.rb +1 -1
- data/lib/active_model/validations/validates.rb +7 -6
- data/lib/active_model/validations.rb +6 -9
- data/lib/active_model/validator.rb +8 -3
- data/lib/active_model.rb +2 -1
- metadata +14 -9
data/lib/active_model/lint.rb
CHANGED
@@ -101,7 +101,7 @@ module ActiveModel
|
|
101
101
|
# locale. If no error is present, the method should return an empty array.
|
102
102
|
def test_errors_aref
|
103
103
|
assert_respond_to model, :errors
|
104
|
-
|
104
|
+
assert_equal [], model.errors[:hello], "errors#[] should return an empty Array"
|
105
105
|
end
|
106
106
|
|
107
107
|
private
|
data/lib/active_model/naming.rb
CHANGED
@@ -8,7 +8,7 @@ module ActiveModel
|
|
8
8
|
class Name
|
9
9
|
include Comparable
|
10
10
|
|
11
|
-
|
11
|
+
attr_accessor :singular, :plural, :element, :collection,
|
12
12
|
:singular_route_key, :route_key, :param_key, :i18n_key,
|
13
13
|
:name
|
14
14
|
|
@@ -110,6 +110,22 @@ module ActiveModel
|
|
110
110
|
# BlogPost.model_name.eql?('BlogPost') # => true
|
111
111
|
# BlogPost.model_name.eql?('Blog Post') # => false
|
112
112
|
|
113
|
+
##
|
114
|
+
# :method: match?
|
115
|
+
#
|
116
|
+
# :call-seq:
|
117
|
+
# match?(regexp)
|
118
|
+
#
|
119
|
+
# Equivalent to <tt>String#match?</tt>. Match the class name against the
|
120
|
+
# given regexp. Returns +true+ if there is a match, otherwise +false+.
|
121
|
+
#
|
122
|
+
# class BlogPost
|
123
|
+
# extend ActiveModel::Naming
|
124
|
+
# end
|
125
|
+
#
|
126
|
+
# BlogPost.model_name.match?(/Post/) # => true
|
127
|
+
# BlogPost.model_name.match?(/\d/) # => false
|
128
|
+
|
113
129
|
##
|
114
130
|
# :method: to_s
|
115
131
|
#
|
@@ -131,7 +147,7 @@ module ActiveModel
|
|
131
147
|
# to_str()
|
132
148
|
#
|
133
149
|
# Equivalent to +to_s+.
|
134
|
-
delegate :==, :===, :<=>, :=~, :"!~", :eql?, :to_s,
|
150
|
+
delegate :==, :===, :<=>, :=~, :"!~", :eql?, :match?, :to_s,
|
135
151
|
:to_str, :as_json, to: :name
|
136
152
|
|
137
153
|
# Returns a new ActiveModel::Name instance. By default, the +namespace+
|
@@ -150,7 +166,7 @@ module ActiveModel
|
|
150
166
|
|
151
167
|
raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if @name.blank?
|
152
168
|
|
153
|
-
@unnamespaced = @name.
|
169
|
+
@unnamespaced = @name.delete_prefix("#{namespace.name}::") if namespace
|
154
170
|
@klass = klass
|
155
171
|
@singular = _singularize(@name)
|
156
172
|
@plural = ActiveSupport::Inflector.pluralize(@singular)
|
@@ -187,13 +203,12 @@ module ActiveModel
|
|
187
203
|
defaults << @human
|
188
204
|
|
189
205
|
options = { scope: [@klass.i18n_scope, :models], count: 1, default: defaults }.merge!(options.except(:default))
|
190
|
-
I18n.translate(defaults.shift, options)
|
206
|
+
I18n.translate(defaults.shift, **options)
|
191
207
|
end
|
192
208
|
|
193
209
|
private
|
194
|
-
|
195
210
|
def _singularize(string)
|
196
|
-
ActiveSupport::Inflector.underscore(string).tr("/"
|
211
|
+
ActiveSupport::Inflector.underscore(string).tr("/", "_")
|
197
212
|
end
|
198
213
|
end
|
199
214
|
|
@@ -236,7 +251,7 @@ module ActiveModel
|
|
236
251
|
# Person.model_name.plural # => "people"
|
237
252
|
def model_name
|
238
253
|
@_model_name ||= begin
|
239
|
-
namespace =
|
254
|
+
namespace = module_parents.detect do |n|
|
240
255
|
n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
|
241
256
|
end
|
242
257
|
ActiveModel::Name.new(self, namespace)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/error"
|
4
|
+
require "forwardable"
|
5
|
+
|
6
|
+
module ActiveModel
|
7
|
+
class NestedError < Error
|
8
|
+
def initialize(base, inner_error, override_options = {})
|
9
|
+
@base = base
|
10
|
+
@inner_error = inner_error
|
11
|
+
@attribute = override_options.fetch(:attribute) { inner_error.attribute }
|
12
|
+
@type = override_options.fetch(:type) { inner_error.type }
|
13
|
+
@raw_type = inner_error.raw_type
|
14
|
+
@options = inner_error.options
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :inner_error
|
18
|
+
|
19
|
+
extend Forwardable
|
20
|
+
def_delegators :@inner_error, :message
|
21
|
+
end
|
22
|
+
end
|
data/lib/active_model/railtie.rb
CHANGED
@@ -7,8 +7,14 @@ module ActiveModel
|
|
7
7
|
class Railtie < Rails::Railtie # :nodoc:
|
8
8
|
config.eager_load_namespaces << ActiveModel
|
9
9
|
|
10
|
+
config.active_model = ActiveSupport::OrderedOptions.new
|
11
|
+
|
10
12
|
initializer "active_model.secure_password" do
|
11
13
|
ActiveModel::SecurePassword.min_cost = Rails.env.test?
|
12
14
|
end
|
15
|
+
|
16
|
+
initializer "active_model.i18n_customize_full_message" do
|
17
|
+
ActiveModel::Error.i18n_customize_full_message = config.active_model.delete(:i18n_customize_full_message) || false
|
18
|
+
end
|
13
19
|
end
|
14
20
|
end
|
@@ -16,15 +16,16 @@ 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 +
|
19
|
+
# This mechanism requires you to have a +XXX_digest+ attribute.
|
20
|
+
# Where +XXX+ is the attribute name of your desired password.
|
20
21
|
#
|
21
22
|
# The following validations are added automatically:
|
22
23
|
# * Password must be present on creation
|
23
24
|
# * Password length should be less than or equal to 72 bytes
|
24
|
-
# * Confirmation of password (using a +
|
25
|
+
# * Confirmation of password (using a +XXX_confirmation+ attribute)
|
25
26
|
#
|
26
|
-
# If
|
27
|
-
# value for +
|
27
|
+
# If confirmation validation is not needed, simply leave out the
|
28
|
+
# value for +XXX_confirmation+ (i.e. don't provide a form field for
|
28
29
|
# it). When this attribute has a +nil+ value, the validation will not be
|
29
30
|
# triggered.
|
30
31
|
#
|
@@ -37,22 +38,27 @@ module ActiveModel
|
|
37
38
|
#
|
38
39
|
# Example using Active Record (which automatically includes ActiveModel::SecurePassword):
|
39
40
|
#
|
40
|
-
# # Schema: User(name:string, password_digest:string)
|
41
|
+
# # Schema: User(name:string, password_digest:string, recovery_password_digest:string)
|
41
42
|
# class User < ActiveRecord::Base
|
42
43
|
# has_secure_password
|
44
|
+
# has_secure_password :recovery_password, validations: false
|
43
45
|
# end
|
44
46
|
#
|
45
47
|
# user = User.new(name: 'david', password: '', password_confirmation: 'nomatch')
|
46
|
-
# user.save
|
48
|
+
# user.save # => false, password required
|
47
49
|
# user.password = 'mUc3m00RsqyRe'
|
48
|
-
# user.save
|
50
|
+
# user.save # => false, confirmation doesn't match
|
49
51
|
# user.password_confirmation = 'mUc3m00RsqyRe'
|
50
|
-
# user.save
|
51
|
-
# user.
|
52
|
-
# user.
|
53
|
-
#
|
54
|
-
#
|
55
|
-
|
52
|
+
# user.save # => true
|
53
|
+
# user.recovery_password = "42password"
|
54
|
+
# user.recovery_password_digest # => "$2a$04$iOfhwahFymCs5weB3BNH/uXkTG65HR.qpW.bNhEjFP3ftli3o5DQC"
|
55
|
+
# user.save # => true
|
56
|
+
# user.authenticate('notright') # => false
|
57
|
+
# user.authenticate('mUc3m00RsqyRe') # => user
|
58
|
+
# user.authenticate_recovery_password('42password') # => user
|
59
|
+
# User.find_by(name: 'david')&.authenticate('notright') # => false
|
60
|
+
# User.find_by(name: 'david')&.authenticate('mUc3m00RsqyRe') # => user
|
61
|
+
def has_secure_password(attribute = :password, validations: true)
|
56
62
|
# Load bcrypt gem only when has_secure_password is used.
|
57
63
|
# This is to avoid ActiveModel (and by extension the entire framework)
|
58
64
|
# being dependent on a binary library.
|
@@ -63,9 +69,9 @@ module ActiveModel
|
|
63
69
|
raise
|
64
70
|
end
|
65
71
|
|
66
|
-
include InstanceMethodsOnActivation
|
72
|
+
include InstanceMethodsOnActivation.new(attribute)
|
67
73
|
|
68
|
-
if
|
74
|
+
if validations
|
69
75
|
include ActiveModel::Validations
|
70
76
|
|
71
77
|
# This ensures the model has a password by checking whether the password_digest
|
@@ -73,56 +79,49 @@ module ActiveModel
|
|
73
79
|
# when there is an error, the message is added to the password attribute instead
|
74
80
|
# so that the error message will make sense to the end-user.
|
75
81
|
validate do |record|
|
76
|
-
record.errors.add(
|
82
|
+
record.errors.add(attribute, :blank) unless record.public_send("#{attribute}_digest").present?
|
77
83
|
end
|
78
84
|
|
79
|
-
validates_length_of
|
80
|
-
validates_confirmation_of
|
85
|
+
validates_length_of attribute, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
|
86
|
+
validates_confirmation_of attribute, allow_blank: true
|
81
87
|
end
|
82
88
|
end
|
83
89
|
end
|
84
90
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
# class User < ActiveRecord::Base
|
89
|
-
# has_secure_password validations: false
|
90
|
-
# end
|
91
|
-
#
|
92
|
-
# user = User.new(name: 'david', password: 'mUc3m00RsqyRe')
|
93
|
-
# user.save
|
94
|
-
# user.authenticate('notright') # => false
|
95
|
-
# user.authenticate('mUc3m00RsqyRe') # => user
|
96
|
-
def authenticate(unencrypted_password)
|
97
|
-
BCrypt::Password.new(password_digest).is_password?(unencrypted_password) && self
|
98
|
-
end
|
91
|
+
class InstanceMethodsOnActivation < Module
|
92
|
+
def initialize(attribute)
|
93
|
+
attr_reader attribute
|
99
94
|
|
100
|
-
|
95
|
+
define_method("#{attribute}=") do |unencrypted_password|
|
96
|
+
if unencrypted_password.nil?
|
97
|
+
self.public_send("#{attribute}_digest=", nil)
|
98
|
+
elsif !unencrypted_password.empty?
|
99
|
+
instance_variable_set("@#{attribute}", unencrypted_password)
|
100
|
+
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
|
101
|
+
self.public_send("#{attribute}_digest=", BCrypt::Password.create(unencrypted_password, cost: cost))
|
102
|
+
end
|
103
|
+
end
|
101
104
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
|
120
|
-
self.password_digest = BCrypt::Password.create(unencrypted_password, cost: cost)
|
105
|
+
define_method("#{attribute}_confirmation=") do |unencrypted_password|
|
106
|
+
instance_variable_set("@#{attribute}_confirmation", unencrypted_password)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns +self+ if the password is correct, otherwise +false+.
|
110
|
+
#
|
111
|
+
# class User < ActiveRecord::Base
|
112
|
+
# has_secure_password validations: false
|
113
|
+
# end
|
114
|
+
#
|
115
|
+
# user = User.new(name: 'david', password: 'mUc3m00RsqyRe')
|
116
|
+
# user.save
|
117
|
+
# user.authenticate_password('notright') # => false
|
118
|
+
# user.authenticate_password('mUc3m00RsqyRe') # => user
|
119
|
+
define_method("authenticate_#{attribute}") do |unencrypted_password|
|
120
|
+
attribute_digest = public_send("#{attribute}_digest")
|
121
|
+
BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self
|
121
122
|
end
|
122
|
-
end
|
123
123
|
|
124
|
-
|
125
|
-
@password_confirmation = unencrypted_password
|
124
|
+
alias_method :authenticate, :authenticate_password if attribute == :password
|
126
125
|
end
|
127
126
|
end
|
128
127
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/core_ext/
|
4
|
-
require "active_support/core_ext/hash/slice"
|
3
|
+
require "active_support/core_ext/enumerable"
|
5
4
|
|
6
5
|
module ActiveModel
|
7
6
|
# == Active \Model \Serialization
|
@@ -124,17 +123,17 @@ module ActiveModel
|
|
124
123
|
# user.serializable_hash(include: { notes: { only: 'title' }})
|
125
124
|
# # => {"name" => "Napoleon", "notes" => [{"title"=>"Battle of Austerlitz"}]}
|
126
125
|
def serializable_hash(options = nil)
|
127
|
-
options ||= {}
|
128
|
-
|
129
126
|
attribute_names = attributes.keys
|
127
|
+
|
128
|
+
return serializable_attributes(attribute_names) if options.blank?
|
129
|
+
|
130
130
|
if only = options[:only]
|
131
131
|
attribute_names &= Array(only).map(&:to_s)
|
132
132
|
elsif except = options[:except]
|
133
133
|
attribute_names -= Array(except).map(&:to_s)
|
134
134
|
end
|
135
135
|
|
136
|
-
hash =
|
137
|
-
attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
|
136
|
+
hash = serializable_attributes(attribute_names)
|
138
137
|
|
139
138
|
Array(options[:methods]).each { |m| hash[m.to_s] = send(m) }
|
140
139
|
|
@@ -150,7 +149,6 @@ module ActiveModel
|
|
150
149
|
end
|
151
150
|
|
152
151
|
private
|
153
|
-
|
154
152
|
# Hook method defining how an attribute value should be retrieved for
|
155
153
|
# serialization. By default this is assumed to be an instance named after
|
156
154
|
# the attribute. Override this method in subclasses should you need to
|
@@ -169,6 +167,10 @@ module ActiveModel
|
|
169
167
|
# end
|
170
168
|
alias :read_attribute_for_serialization :send
|
171
169
|
|
170
|
+
def serializable_attributes(attribute_names)
|
171
|
+
attribute_names.index_with { |n| read_attribute_for_serialization(n) }
|
172
|
+
end
|
173
|
+
|
172
174
|
# Add associations specified via the <tt>:include</tt> option.
|
173
175
|
#
|
174
176
|
# Expects a block that takes as arguments:
|
@@ -26,13 +26,13 @@ module ActiveModel
|
|
26
26
|
# user = User.find(1)
|
27
27
|
# user.as_json
|
28
28
|
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
|
29
|
-
# # "created_at" => "2006
|
29
|
+
# # "created_at" => "2006-08-01T17:27:133.000Z", "awesome" => true}
|
30
30
|
#
|
31
31
|
# ActiveRecord::Base.include_root_in_json = true
|
32
32
|
#
|
33
33
|
# user.as_json
|
34
34
|
# # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
|
35
|
-
# # "created_at" => "2006
|
35
|
+
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true } }
|
36
36
|
#
|
37
37
|
# This behavior can also be achieved by setting the <tt>:root</tt> option
|
38
38
|
# to +true+ as in:
|
@@ -40,7 +40,14 @@ module ActiveModel
|
|
40
40
|
# user = User.find(1)
|
41
41
|
# user.as_json(root: true)
|
42
42
|
# # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
|
43
|
-
# # "created_at" => "2006
|
43
|
+
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true } }
|
44
|
+
#
|
45
|
+
# If you prefer, <tt>:root</tt> may also be set to a custom string key instead as in:
|
46
|
+
#
|
47
|
+
# user = User.find(1)
|
48
|
+
# user.as_json(root: "author")
|
49
|
+
# # => { "author" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
|
50
|
+
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true } }
|
44
51
|
#
|
45
52
|
# Without any +options+, the returned Hash will include all the model's
|
46
53
|
# attributes.
|
@@ -48,7 +55,7 @@ module ActiveModel
|
|
48
55
|
# user = User.find(1)
|
49
56
|
# user.as_json
|
50
57
|
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
|
51
|
-
# # "created_at" => "2006
|
58
|
+
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true}
|
52
59
|
#
|
53
60
|
# The <tt>:only</tt> and <tt>:except</tt> options can be used to limit
|
54
61
|
# the attributes included, and work similar to the +attributes+ method.
|
@@ -63,14 +70,14 @@ module ActiveModel
|
|
63
70
|
#
|
64
71
|
# user.as_json(methods: :permalink)
|
65
72
|
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
|
66
|
-
# # "created_at" => "2006
|
73
|
+
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true,
|
67
74
|
# # "permalink" => "1-konata-izumi" }
|
68
75
|
#
|
69
76
|
# To include associations use <tt>:include</tt>:
|
70
77
|
#
|
71
78
|
# user.as_json(include: :posts)
|
72
79
|
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
|
73
|
-
# # "created_at" => "2006
|
80
|
+
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true,
|
74
81
|
# # "posts" => [ { "id" => 1, "author_id" => 1, "title" => "Welcome to the weblog" },
|
75
82
|
# # { "id" => 2, "author_id" => 1, "title" => "So I was thinking" } ] }
|
76
83
|
#
|
@@ -81,7 +88,7 @@ module ActiveModel
|
|
81
88
|
# only: :body } },
|
82
89
|
# only: :title } })
|
83
90
|
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
|
84
|
-
# # "created_at" => "2006
|
91
|
+
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true,
|
85
92
|
# # "posts" => [ { "comments" => [ { "body" => "1st post!" }, { "body" => "Second!" } ],
|
86
93
|
# # "title" => "Welcome to the weblog" },
|
87
94
|
# # { "comments" => [ { "body" => "Don't think too hard" } ],
|
@@ -93,11 +100,12 @@ module ActiveModel
|
|
93
100
|
include_root_in_json
|
94
101
|
end
|
95
102
|
|
103
|
+
hash = serializable_hash(options).as_json
|
96
104
|
if root
|
97
105
|
root = model_name.element if root == true
|
98
|
-
{ root =>
|
106
|
+
{ root => hash }
|
99
107
|
else
|
100
|
-
|
108
|
+
hash
|
101
109
|
end
|
102
110
|
end
|
103
111
|
|
@@ -10,16 +10,11 @@ module ActiveModel
|
|
10
10
|
:date
|
11
11
|
end
|
12
12
|
|
13
|
-
def serialize(value)
|
14
|
-
cast(value)
|
15
|
-
end
|
16
|
-
|
17
13
|
def type_cast_for_schema(value)
|
18
14
|
value.to_s(:db).inspect
|
19
15
|
end
|
20
16
|
|
21
17
|
private
|
22
|
-
|
23
18
|
def cast_value(value)
|
24
19
|
if value.is_a?(::String)
|
25
20
|
return if value.empty?
|
@@ -13,12 +13,7 @@ module ActiveModel
|
|
13
13
|
:datetime
|
14
14
|
end
|
15
15
|
|
16
|
-
def serialize(value)
|
17
|
-
super(cast(value))
|
18
|
-
end
|
19
|
-
|
20
16
|
private
|
21
|
-
|
22
17
|
def cast_value(value)
|
23
18
|
return apply_seconds_precision(value) unless value.is_a?(::String)
|
24
19
|
return if value.empty?
|
@@ -40,9 +35,9 @@ module ActiveModel
|
|
40
35
|
end
|
41
36
|
|
42
37
|
def value_from_multiparameter_assignment(values_hash)
|
43
|
-
|
44
|
-
|
45
|
-
raise ArgumentError,
|
38
|
+
missing_parameters = [1, 2, 3].delete_if { |key| values_hash.key?(key) }
|
39
|
+
unless missing_parameters.empty?
|
40
|
+
raise ArgumentError, "Provided hash #{values_hash} doesn't contain necessary keys: #{missing_parameters}"
|
46
41
|
end
|
47
42
|
super
|
48
43
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/core_ext/object/try"
|
4
|
+
|
3
5
|
module ActiveModel
|
4
6
|
module Type
|
5
7
|
class Float < Value # :nodoc:
|
@@ -18,10 +20,7 @@ module ActiveModel
|
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
21
|
-
alias serialize cast
|
22
|
-
|
23
23
|
private
|
24
|
-
|
25
24
|
def cast_value(value)
|
26
25
|
case value
|
27
26
|
when ::Float then value
|
@@ -4,8 +4,12 @@ module ActiveModel
|
|
4
4
|
module Type
|
5
5
|
module Helpers # :nodoc: all
|
6
6
|
class AcceptsMultiparameterTime < Module
|
7
|
-
|
8
|
-
|
7
|
+
module InstanceMethods
|
8
|
+
def serialize(value)
|
9
|
+
super(cast(value))
|
10
|
+
end
|
11
|
+
|
12
|
+
def cast(value)
|
9
13
|
if value.is_a?(Hash)
|
10
14
|
value_from_multiparameter_assignment(value)
|
11
15
|
else
|
@@ -13,7 +17,7 @@ module ActiveModel
|
|
13
17
|
end
|
14
18
|
end
|
15
19
|
|
16
|
-
|
20
|
+
def assert_valid_value(value)
|
17
21
|
if value.is_a?(Hash)
|
18
22
|
value_from_multiparameter_assignment(value)
|
19
23
|
else
|
@@ -21,17 +25,21 @@ module ActiveModel
|
|
21
25
|
end
|
22
26
|
end
|
23
27
|
|
24
|
-
|
28
|
+
def value_constructed_by_mass_assignment?(value)
|
25
29
|
value.is_a?(Hash)
|
26
30
|
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(defaults: {})
|
34
|
+
include InstanceMethods
|
27
35
|
|
28
36
|
define_method(:value_from_multiparameter_assignment) do |values_hash|
|
29
37
|
defaults.each do |k, v|
|
30
38
|
values_hash[k] ||= v
|
31
39
|
end
|
32
40
|
return unless values_hash[1] && values_hash[2] && values_hash[3]
|
33
|
-
values = values_hash.sort.map(&:last)
|
34
|
-
::Time.
|
41
|
+
values = values_hash.sort.map!(&:last)
|
42
|
+
::Time.public_send(default_timezone, *values)
|
35
43
|
end
|
36
44
|
private :value_from_multiparameter_assignment
|
37
45
|
end
|
@@ -4,14 +4,23 @@ module ActiveModel
|
|
4
4
|
module Type
|
5
5
|
module Helpers # :nodoc: all
|
6
6
|
module Numeric
|
7
|
+
def serialize(value)
|
8
|
+
cast(value)
|
9
|
+
end
|
10
|
+
|
7
11
|
def cast(value)
|
8
|
-
value
|
12
|
+
# Checks whether the value is numeric. Spaceship operator
|
13
|
+
# will return nil if value is not numeric.
|
14
|
+
value = if value <=> 0
|
15
|
+
value
|
16
|
+
else
|
9
17
|
case value
|
10
18
|
when true then 1
|
11
19
|
when false then 0
|
12
|
-
|
13
|
-
else value
|
20
|
+
else value.presence
|
14
21
|
end
|
22
|
+
end
|
23
|
+
|
15
24
|
super(value)
|
16
25
|
end
|
17
26
|
|
@@ -20,17 +29,19 @@ module ActiveModel
|
|
20
29
|
end
|
21
30
|
|
22
31
|
private
|
23
|
-
|
24
32
|
def number_to_non_number?(old_value, new_value_before_type_cast)
|
25
|
-
old_value != nil && non_numeric_string?(new_value_before_type_cast)
|
33
|
+
old_value != nil && non_numeric_string?(new_value_before_type_cast.to_s)
|
26
34
|
end
|
27
35
|
|
28
36
|
def non_numeric_string?(value)
|
29
37
|
# 'wibble'.to_i will give zero, we want to make sure
|
30
38
|
# that we aren't marking int zero to string zero as
|
31
39
|
# changed.
|
32
|
-
|
40
|
+
!NUMERIC_REGEX.match?(value)
|
33
41
|
end
|
42
|
+
|
43
|
+
NUMERIC_REGEX = /\A\s*[+-]?\d/
|
44
|
+
private_constant :NUMERIC_REGEX
|
34
45
|
end
|
35
46
|
end
|
36
47
|
end
|