activemodel 5.2.7.1 → 6.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +38 -158
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/lib/active_model/attribute/user_provided_default.rb +1 -2
- data/lib/active_model/attribute.rb +3 -4
- data/lib/active_model/attribute_assignment.rb +1 -1
- data/lib/active_model/attribute_methods.rb +39 -1
- data/lib/active_model/attribute_mutation_tracker.rb +1 -6
- data/lib/active_model/attribute_set/builder.rb +1 -3
- data/lib/active_model/attribute_set/yaml_encoder.rb +1 -2
- data/lib/active_model/attribute_set.rb +2 -10
- data/lib/active_model/attributes.rb +10 -22
- data/lib/active_model/callbacks.rb +10 -7
- data/lib/active_model/conversion.rb +1 -1
- data/lib/active_model/dirty.rb +2 -2
- data/lib/active_model/errors.rb +90 -11
- data/lib/active_model/gem_version.rb +4 -4
- data/lib/active_model/naming.rb +19 -3
- data/lib/active_model/railtie.rb +6 -0
- data/lib/active_model/secure_password.rb +48 -55
- data/lib/active_model/serializers/json.rb +10 -9
- data/lib/active_model/type/binary.rb +1 -1
- data/lib/active_model/type/boolean.rb +1 -10
- data/lib/active_model/type/date.rb +1 -2
- data/lib/active_model/type/date_time.rb +3 -4
- data/lib/active_model/type/decimal.rb +4 -0
- data/lib/active_model/type/helpers/time_value.rb +19 -1
- data/lib/active_model/type/helpers.rb +0 -1
- data/lib/active_model/type/integer.rb +1 -6
- data/lib/active_model/type/registry.rb +2 -10
- data/lib/active_model/type/string.rb +2 -2
- data/lib/active_model/type/time.rb +0 -5
- data/lib/active_model/validations/acceptance.rb +4 -8
- data/lib/active_model/validations/clusivity.rb +1 -1
- data/lib/active_model/validations/confirmation.rb +2 -2
- data/lib/active_model/validations/inclusion.rb +1 -1
- data/lib/active_model/validations/numericality.rb +9 -6
- data/lib/active_model/validations/validates.rb +2 -2
- data/lib/active_model/validations.rb +0 -2
- data/lib/active_model/validator.rb +1 -1
- data/lib/active_model.rb +1 -1
- metadata +13 -14
- data/lib/active_model/type/helpers/timezone.rb +0 -19
data/lib/active_model/naming.rb
CHANGED
@@ -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+
|
@@ -193,7 +209,7 @@ module ActiveModel
|
|
193
209
|
private
|
194
210
|
|
195
211
|
def _singularize(string)
|
196
|
-
ActiveSupport::Inflector.underscore(string).tr("/"
|
212
|
+
ActiveSupport::Inflector.underscore(string).tr("/", "_")
|
197
213
|
end
|
198
214
|
end
|
199
215
|
|
@@ -236,7 +252,7 @@ module ActiveModel
|
|
236
252
|
# Person.model_name.plural # => "people"
|
237
253
|
def model_name
|
238
254
|
@_model_name ||= begin
|
239
|
-
namespace =
|
255
|
+
namespace = module_parents.detect do |n|
|
240
256
|
n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
|
241
257
|
end
|
242
258
|
ActiveModel::Name.new(self, namespace)
|
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_full_message" do
|
17
|
+
ActiveModel::Errors.i18n_full_message = config.active_model.delete(:i18n_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,9 +38,10 @@ 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')
|
@@ -48,11 +50,15 @@ module ActiveModel
|
|
48
50
|
# user.save # => false, confirmation doesn't match
|
49
51
|
# user.password_confirmation = 'mUc3m00RsqyRe'
|
50
52
|
# user.save # => true
|
53
|
+
# user.recovery_password = "42password"
|
54
|
+
# user.recovery_password_digest # => "$2a$04$iOfhwahFymCs5weB3BNH/uXkTG65HR.qpW.bNhEjFP3ftli3o5DQC"
|
55
|
+
# user.save # => true
|
51
56
|
# user.authenticate('notright') # => false
|
52
57
|
# user.authenticate('mUc3m00RsqyRe') # => user
|
58
|
+
# user.authenticate_recovery_password('42password') # => user
|
53
59
|
# User.find_by(name: 'david').try(:authenticate, 'notright') # => false
|
54
60
|
# User.find_by(name: 'david').try(:authenticate, 'mUc3m00RsqyRe') # => user
|
55
|
-
def has_secure_password(
|
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,40 @@ module ActiveModel
|
|
63
69
|
raise
|
64
70
|
end
|
65
71
|
|
66
|
-
|
72
|
+
attr_reader attribute
|
73
|
+
|
74
|
+
define_method("#{attribute}=") do |unencrypted_password|
|
75
|
+
if unencrypted_password.nil?
|
76
|
+
self.send("#{attribute}_digest=", nil)
|
77
|
+
elsif !unencrypted_password.empty?
|
78
|
+
instance_variable_set("@#{attribute}", unencrypted_password)
|
79
|
+
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
|
80
|
+
self.send("#{attribute}_digest=", BCrypt::Password.create(unencrypted_password, cost: cost))
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
define_method("#{attribute}_confirmation=") do |unencrypted_password|
|
85
|
+
instance_variable_set("@#{attribute}_confirmation", unencrypted_password)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns +self+ if the password is correct, otherwise +false+.
|
89
|
+
#
|
90
|
+
# class User < ActiveRecord::Base
|
91
|
+
# has_secure_password validations: false
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# user = User.new(name: 'david', password: 'mUc3m00RsqyRe')
|
95
|
+
# user.save
|
96
|
+
# user.authenticate_password('notright') # => false
|
97
|
+
# user.authenticate_password('mUc3m00RsqyRe') # => user
|
98
|
+
define_method("authenticate_#{attribute}") do |unencrypted_password|
|
99
|
+
attribute_digest = send("#{attribute}_digest")
|
100
|
+
BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self
|
101
|
+
end
|
102
|
+
|
103
|
+
alias_method :authenticate, :authenticate_password if attribute == :password
|
67
104
|
|
68
|
-
if
|
105
|
+
if validations
|
69
106
|
include ActiveModel::Validations
|
70
107
|
|
71
108
|
# This ensures the model has a password by checking whether the password_digest
|
@@ -73,57 +110,13 @@ module ActiveModel
|
|
73
110
|
# when there is an error, the message is added to the password attribute instead
|
74
111
|
# so that the error message will make sense to the end-user.
|
75
112
|
validate do |record|
|
76
|
-
record.errors.add(
|
113
|
+
record.errors.add(attribute, :blank) unless record.send("#{attribute}_digest").present?
|
77
114
|
end
|
78
115
|
|
79
|
-
validates_length_of
|
80
|
-
validates_confirmation_of
|
116
|
+
validates_length_of attribute, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
|
117
|
+
validates_confirmation_of attribute, allow_blank: true
|
81
118
|
end
|
82
119
|
end
|
83
120
|
end
|
84
|
-
|
85
|
-
module InstanceMethodsOnActivation
|
86
|
-
# Returns +self+ if the password is correct, otherwise +false+.
|
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
|
99
|
-
|
100
|
-
attr_reader :password
|
101
|
-
|
102
|
-
# Encrypts the password into the +password_digest+ attribute, only if the
|
103
|
-
# new password is not empty.
|
104
|
-
#
|
105
|
-
# class User < ActiveRecord::Base
|
106
|
-
# has_secure_password validations: false
|
107
|
-
# end
|
108
|
-
#
|
109
|
-
# user = User.new
|
110
|
-
# user.password = nil
|
111
|
-
# user.password_digest # => nil
|
112
|
-
# user.password = 'mUc3m00RsqyRe'
|
113
|
-
# user.password_digest # => "$2a$10$4LEA7r4YmNHtvlAvHhsYAeZmk/xeUVtMTYqwIvYY76EW5GUqDiP4."
|
114
|
-
def password=(unencrypted_password)
|
115
|
-
if unencrypted_password.nil?
|
116
|
-
self.password_digest = nil
|
117
|
-
elsif !unencrypted_password.empty?
|
118
|
-
@password = unencrypted_password
|
119
|
-
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
|
120
|
-
self.password_digest = BCrypt::Password.create(unencrypted_password, cost: cost)
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
def password_confirmation=(unencrypted_password)
|
125
|
-
@password_confirmation = unencrypted_password
|
126
|
-
end
|
127
|
-
end
|
128
121
|
end
|
129
122
|
end
|
@@ -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,7 @@ 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
44
|
#
|
45
45
|
# Without any +options+, the returned Hash will include all the model's
|
46
46
|
# attributes.
|
@@ -48,7 +48,7 @@ module ActiveModel
|
|
48
48
|
# user = User.find(1)
|
49
49
|
# user.as_json
|
50
50
|
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
|
51
|
-
# # "created_at" => "2006
|
51
|
+
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true}
|
52
52
|
#
|
53
53
|
# The <tt>:only</tt> and <tt>:except</tt> options can be used to limit
|
54
54
|
# the attributes included, and work similar to the +attributes+ method.
|
@@ -63,14 +63,14 @@ module ActiveModel
|
|
63
63
|
#
|
64
64
|
# user.as_json(methods: :permalink)
|
65
65
|
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
|
66
|
-
# # "created_at" => "2006
|
66
|
+
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true,
|
67
67
|
# # "permalink" => "1-konata-izumi" }
|
68
68
|
#
|
69
69
|
# To include associations use <tt>:include</tt>:
|
70
70
|
#
|
71
71
|
# user.as_json(include: :posts)
|
72
72
|
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
|
73
|
-
# # "created_at" => "2006
|
73
|
+
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true,
|
74
74
|
# # "posts" => [ { "id" => 1, "author_id" => 1, "title" => "Welcome to the weblog" },
|
75
75
|
# # { "id" => 2, "author_id" => 1, "title" => "So I was thinking" } ] }
|
76
76
|
#
|
@@ -81,7 +81,7 @@ module ActiveModel
|
|
81
81
|
# only: :body } },
|
82
82
|
# only: :title } })
|
83
83
|
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
|
84
|
-
# # "created_at" => "2006
|
84
|
+
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true,
|
85
85
|
# # "posts" => [ { "comments" => [ { "body" => "1st post!" }, { "body" => "Second!" } ],
|
86
86
|
# # "title" => "Welcome to the weblog" },
|
87
87
|
# # { "comments" => [ { "body" => "Don't think too hard" } ],
|
@@ -93,11 +93,12 @@ module ActiveModel
|
|
93
93
|
include_root_in_json
|
94
94
|
end
|
95
95
|
|
96
|
+
hash = serializable_hash(options).as_json
|
96
97
|
if root
|
97
98
|
root = model_name.element if root == true
|
98
|
-
{ root =>
|
99
|
+
{ root => hash }
|
99
100
|
else
|
100
|
-
|
101
|
+
hash
|
101
102
|
end
|
102
103
|
end
|
103
104
|
|
@@ -14,16 +14,7 @@ module ActiveModel
|
|
14
14
|
# - Empty strings are coerced to +nil+
|
15
15
|
# - All other values will be coerced to +true+
|
16
16
|
class Boolean < Value
|
17
|
-
FALSE_VALUES = [
|
18
|
-
false, 0,
|
19
|
-
"0", :"0",
|
20
|
-
"f", :f,
|
21
|
-
"F", :F,
|
22
|
-
"false", :false,
|
23
|
-
"FALSE", :FALSE,
|
24
|
-
"off", :off,
|
25
|
-
"OFF", :OFF,
|
26
|
-
].to_set.freeze
|
17
|
+
FALSE_VALUES = [false, 0, "0", "f", "F", "false", "FALSE", "off", "OFF"].to_set
|
27
18
|
|
28
19
|
def type # :nodoc:
|
29
20
|
:boolean
|
@@ -3,7 +3,6 @@
|
|
3
3
|
module ActiveModel
|
4
4
|
module Type
|
5
5
|
class Date < Value # :nodoc:
|
6
|
-
include Helpers::Timezone
|
7
6
|
include Helpers::AcceptsMultiparameterTime.new
|
8
7
|
|
9
8
|
def type
|
@@ -50,7 +49,7 @@ module ActiveModel
|
|
50
49
|
|
51
50
|
def value_from_multiparameter_assignment(*)
|
52
51
|
time = super
|
53
|
-
time &&
|
52
|
+
time && time.to_date
|
54
53
|
end
|
55
54
|
end
|
56
55
|
end
|
@@ -3,7 +3,6 @@
|
|
3
3
|
module ActiveModel
|
4
4
|
module Type
|
5
5
|
class DateTime < Value # :nodoc:
|
6
|
-
include Helpers::Timezone
|
7
6
|
include Helpers::TimeValue
|
8
7
|
include Helpers::AcceptsMultiparameterTime.new(
|
9
8
|
defaults: { 4 => 0, 5 => 0 }
|
@@ -40,9 +39,9 @@ module ActiveModel
|
|
40
39
|
end
|
41
40
|
|
42
41
|
def value_from_multiparameter_assignment(values_hash)
|
43
|
-
|
44
|
-
if
|
45
|
-
raise ArgumentError,
|
42
|
+
missing_parameters = (1..3).select { |key| !values_hash.key?(key) }
|
43
|
+
if missing_parameters.any?
|
44
|
+
raise ArgumentError, "Provided hash #{values_hash} doesn't contain necessary keys: #{missing_parameters}"
|
46
45
|
end
|
47
46
|
super
|
48
47
|
end
|
@@ -21,6 +21,18 @@ module ActiveModel
|
|
21
21
|
value
|
22
22
|
end
|
23
23
|
|
24
|
+
def is_utc?
|
25
|
+
::Time.zone_default.nil? || ::Time.zone_default =~ "UTC"
|
26
|
+
end
|
27
|
+
|
28
|
+
def default_timezone
|
29
|
+
if is_utc?
|
30
|
+
:utc
|
31
|
+
else
|
32
|
+
:local
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
24
36
|
def apply_seconds_precision(value)
|
25
37
|
return value unless precision && value.respond_to?(:usec)
|
26
38
|
number_of_insignificant_digits = 6 - precision
|
@@ -58,7 +70,13 @@ module ActiveModel
|
|
58
70
|
# Doesn't handle time zones.
|
59
71
|
def fast_string_to_time(string)
|
60
72
|
if string =~ ISO_DATETIME
|
61
|
-
|
73
|
+
microsec_part = $7
|
74
|
+
if microsec_part && microsec_part.start_with?(".") && microsec_part.length == 7
|
75
|
+
microsec_part[0] = ""
|
76
|
+
microsec = microsec_part.to_i
|
77
|
+
else
|
78
|
+
microsec = (microsec_part.to_r * 1_000_000).to_i
|
79
|
+
end
|
62
80
|
new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
|
63
81
|
end
|
64
82
|
end
|
@@ -31,13 +31,8 @@ module ActiveModel
|
|
31
31
|
result
|
32
32
|
end
|
33
33
|
|
34
|
-
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
35
|
-
# Workaround for Ruby 2.2 "private attribute?" warning.
|
36
|
-
protected
|
37
|
-
|
38
|
-
attr_reader :range
|
39
|
-
|
40
34
|
private
|
35
|
+
attr_reader :range
|
41
36
|
|
42
37
|
def cast_value(value)
|
43
38
|
case value
|
@@ -23,13 +23,8 @@ module ActiveModel
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
27
|
-
# Workaround for Ruby 2.2 "private attribute?" warning.
|
28
|
-
protected
|
29
|
-
|
30
|
-
attr_reader :registrations
|
31
|
-
|
32
26
|
private
|
27
|
+
attr_reader :registrations
|
33
28
|
|
34
29
|
def registration_klass
|
35
30
|
Registration
|
@@ -59,10 +54,7 @@ module ActiveModel
|
|
59
54
|
type_name == name
|
60
55
|
end
|
61
56
|
|
62
|
-
|
63
|
-
# Workaround for Ruby 2.2 "private attribute?" warning.
|
64
|
-
protected
|
65
|
-
|
57
|
+
private
|
66
58
|
attr_reader :name, :block
|
67
59
|
end
|
68
60
|
end
|
@@ -3,7 +3,6 @@
|
|
3
3
|
module ActiveModel
|
4
4
|
module Type
|
5
5
|
class Time < Value # :nodoc:
|
6
|
-
include Helpers::Timezone
|
7
6
|
include Helpers::TimeValue
|
8
7
|
include Helpers::AcceptsMultiparameterTime.new(
|
9
8
|
defaults: { 1 => 1970, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
|
@@ -13,10 +12,6 @@ module ActiveModel
|
|
13
12
|
:time
|
14
13
|
end
|
15
14
|
|
16
|
-
def serialize(value)
|
17
|
-
super(cast(value))
|
18
|
-
end
|
19
|
-
|
20
15
|
def user_input_in_time_zone(value)
|
21
16
|
return unless value.present?
|
22
17
|
|
@@ -54,17 +54,13 @@ module ActiveModel
|
|
54
54
|
def define_on(klass)
|
55
55
|
attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
|
56
56
|
attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
|
57
|
-
klass.
|
58
|
-
klass.
|
57
|
+
klass.define_attribute_methods
|
58
|
+
klass.attr_reader(*attr_readers)
|
59
|
+
klass.attr_writer(*attr_writers)
|
59
60
|
end
|
60
61
|
|
61
|
-
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
62
|
-
# Workaround for Ruby 2.2 "private attribute?" warning.
|
63
|
-
protected
|
64
|
-
|
65
|
-
attr_reader :attributes
|
66
|
-
|
67
62
|
private
|
63
|
+
attr_reader :attributes
|
68
64
|
|
69
65
|
def convert_to_reader_name(method_name)
|
70
66
|
method_name.to_s.chomp("=")
|
@@ -32,7 +32,7 @@ module ActiveModel
|
|
32
32
|
@delimiter ||= options[:in] || options[:within]
|
33
33
|
end
|
34
34
|
|
35
|
-
#
|
35
|
+
# After Ruby 2.2, <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
|
36
36
|
# possible values in the range for equality, which is slower but more accurate.
|
37
37
|
# <tt>Range#cover?</tt> uses the previous logic of comparing a value with the range
|
38
38
|
# endpoints, which is fast but is only accurate on Numeric, Time, Date,
|
@@ -19,11 +19,11 @@ module ActiveModel
|
|
19
19
|
|
20
20
|
private
|
21
21
|
def setup!(klass)
|
22
|
-
klass.
|
22
|
+
klass.attr_reader(*attributes.map do |attribute|
|
23
23
|
:"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation")
|
24
24
|
end.compact)
|
25
25
|
|
26
|
-
klass.
|
26
|
+
klass.attr_writer(*attributes.map do |attribute|
|
27
27
|
:"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=")
|
28
28
|
end.compact)
|
29
29
|
end
|
@@ -19,7 +19,7 @@ module ActiveModel
|
|
19
19
|
# particular enumerable object.
|
20
20
|
#
|
21
21
|
# class Person < ActiveRecord::Base
|
22
|
-
# validates_inclusion_of :
|
22
|
+
# validates_inclusion_of :role, in: %w( admin contributor )
|
23
23
|
# validates_inclusion_of :age, in: 0..99
|
24
24
|
# validates_inclusion_of :format, in: %w( jpg gif png ), message: "extension %{value} is not included in the list"
|
25
25
|
# validates_inclusion_of :states, in: ->(person) { STATES[person.country] }
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "bigdecimal/util"
|
4
|
-
|
5
3
|
module ActiveModel
|
6
4
|
module Validations
|
7
5
|
class NumericalityValidator < EachValidator # :nodoc:
|
@@ -12,6 +10,7 @@ module ActiveModel
|
|
12
10
|
RESERVED_OPTIONS = CHECKS.keys + [:only_integer]
|
13
11
|
|
14
12
|
INTEGER_REGEX = /\A[+-]?\d+\z/
|
13
|
+
DECIMAL_REGEX = /\A[+-]?\d+\.?\d*(e|e[+-])?\d+\z/
|
15
14
|
|
16
15
|
def check_validity!
|
17
16
|
keys = CHECKS.keys - [:odd, :even]
|
@@ -93,17 +92,21 @@ module ActiveModel
|
|
93
92
|
raw_value
|
94
93
|
elsif is_integer?(raw_value)
|
95
94
|
raw_value.to_i
|
96
|
-
elsif !is_hexadecimal_literal?(raw_value)
|
97
|
-
|
95
|
+
elsif is_decimal?(raw_value) && !is_hexadecimal_literal?(raw_value)
|
96
|
+
BigDecimal(raw_value)
|
98
97
|
end
|
99
98
|
end
|
100
99
|
|
101
100
|
def is_integer?(raw_value)
|
102
|
-
INTEGER_REGEX
|
101
|
+
INTEGER_REGEX.match?(raw_value.to_s)
|
102
|
+
end
|
103
|
+
|
104
|
+
def is_decimal?(raw_value)
|
105
|
+
DECIMAL_REGEX.match?(raw_value.to_s)
|
103
106
|
end
|
104
107
|
|
105
108
|
def is_hexadecimal_literal?(raw_value)
|
106
|
-
/\A0[xX]
|
109
|
+
/\A0[xX]/.match?(raw_value)
|
107
110
|
end
|
108
111
|
|
109
112
|
def filtered_options(value)
|
@@ -63,7 +63,7 @@ module ActiveModel
|
|
63
63
|
# and strings in shortcut form.
|
64
64
|
#
|
65
65
|
# validates :email, format: /@/
|
66
|
-
# validates :
|
66
|
+
# validates :role, inclusion: %(admin contributor)
|
67
67
|
# validates :password, length: 6..20
|
68
68
|
#
|
69
69
|
# When using shortcut form, ranges and arrays are passed to your
|
@@ -116,7 +116,7 @@ module ActiveModel
|
|
116
116
|
key = "#{key.to_s.camelize}Validator"
|
117
117
|
|
118
118
|
begin
|
119
|
-
validator = key.include?("::"
|
119
|
+
validator = key.include?("::") ? key.constantize : const_get(key)
|
120
120
|
rescue NameError
|
121
121
|
raise ArgumentError, "Unknown validator: '#{key}'"
|
122
122
|
end
|
data/lib/active_model.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#--
|
4
|
-
# Copyright (c) 2004-
|
4
|
+
# Copyright (c) 2004-2019 David Heinemeier Hansson
|
5
5
|
#
|
6
6
|
# Permission is hereby granted, free of charge, to any person obtaining
|
7
7
|
# a copy of this software and associated documentation files (the
|