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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -158
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/active_model/attribute/user_provided_default.rb +1 -2
  6. data/lib/active_model/attribute.rb +3 -4
  7. data/lib/active_model/attribute_assignment.rb +1 -1
  8. data/lib/active_model/attribute_methods.rb +39 -1
  9. data/lib/active_model/attribute_mutation_tracker.rb +1 -6
  10. data/lib/active_model/attribute_set/builder.rb +1 -3
  11. data/lib/active_model/attribute_set/yaml_encoder.rb +1 -2
  12. data/lib/active_model/attribute_set.rb +2 -10
  13. data/lib/active_model/attributes.rb +10 -22
  14. data/lib/active_model/callbacks.rb +10 -7
  15. data/lib/active_model/conversion.rb +1 -1
  16. data/lib/active_model/dirty.rb +2 -2
  17. data/lib/active_model/errors.rb +90 -11
  18. data/lib/active_model/gem_version.rb +4 -4
  19. data/lib/active_model/naming.rb +19 -3
  20. data/lib/active_model/railtie.rb +6 -0
  21. data/lib/active_model/secure_password.rb +48 -55
  22. data/lib/active_model/serializers/json.rb +10 -9
  23. data/lib/active_model/type/binary.rb +1 -1
  24. data/lib/active_model/type/boolean.rb +1 -10
  25. data/lib/active_model/type/date.rb +1 -2
  26. data/lib/active_model/type/date_time.rb +3 -4
  27. data/lib/active_model/type/decimal.rb +4 -0
  28. data/lib/active_model/type/helpers/time_value.rb +19 -1
  29. data/lib/active_model/type/helpers.rb +0 -1
  30. data/lib/active_model/type/integer.rb +1 -6
  31. data/lib/active_model/type/registry.rb +2 -10
  32. data/lib/active_model/type/string.rb +2 -2
  33. data/lib/active_model/type/time.rb +0 -5
  34. data/lib/active_model/validations/acceptance.rb +4 -8
  35. data/lib/active_model/validations/clusivity.rb +1 -1
  36. data/lib/active_model/validations/confirmation.rb +2 -2
  37. data/lib/active_model/validations/inclusion.rb +1 -1
  38. data/lib/active_model/validations/numericality.rb +9 -6
  39. data/lib/active_model/validations/validates.rb +2 -2
  40. data/lib/active_model/validations.rb +0 -2
  41. data/lib/active_model/validator.rb +1 -1
  42. data/lib/active_model.rb +1 -1
  43. metadata +13 -14
  44. data/lib/active_model/type/helpers/timezone.rb +0 -19
@@ -7,10 +7,10 @@ module ActiveModel
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 5
11
- MINOR = 2
12
- TINY = 7
13
- PRE = "1"
10
+ MAJOR = 6
11
+ MINOR = 0
12
+ TINY = 0
13
+ PRE = "beta1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -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("/".freeze, "_".freeze)
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 = parents.detect do |n|
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)
@@ -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 +password_digest+ attribute.
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 +password_confirmation+ attribute)
25
+ # * Confirmation of password (using a +XXX_confirmation+ attribute)
25
26
  #
26
- # If password confirmation validation is not needed, simply leave out the
27
- # value for +password_confirmation+ (i.e. don't provide a form field 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(options = {})
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
- include InstanceMethodsOnActivation
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 options.fetch(:validations, true)
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(:password, :blank) unless record.password_digest.present?
113
+ record.errors.add(attribute, :blank) unless record.send("#{attribute}_digest").present?
77
114
  end
78
115
 
79
- validates_length_of :password, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
80
- validates_confirmation_of :password, allow_blank: true
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/08/01", "awesome" => true}
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/08/01", "awesome" => true } }
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/08/01", "awesome" => true } }
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/08/01", "awesome" => true}
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/08/01", "awesome" => true,
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/08/01", "awesome" => true,
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/08/01", "awesome" => true,
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 => serializable_hash(options) }
99
+ { root => hash }
99
100
  else
100
- serializable_hash(options)
101
+ hash
101
102
  end
102
103
  end
103
104
 
@@ -40,7 +40,7 @@ module ActiveModel
40
40
  alias_method :to_str, :to_s
41
41
 
42
42
  def hex
43
- @value.unpack("H*")[0]
43
+ @value.unpack1("H*")
44
44
  end
45
45
 
46
46
  def ==(other)
@@ -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 && new_date(time.year, time.mon, time.mday)
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
- missing_parameter = (1..3).detect { |key| !values_hash.key?(key) }
44
- if missing_parameter
45
- raise ArgumentError, missing_parameter
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
@@ -12,6 +12,10 @@ module ActiveModel
12
12
  :decimal
13
13
  end
14
14
 
15
+ def serialize(value)
16
+ cast(value)
17
+ end
18
+
15
19
  def type_cast_for_schema(value)
16
20
  value.to_s.inspect
17
21
  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
- microsec = ($7.to_r * 1_000_000).to_i
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
@@ -4,4 +4,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
6
  require "active_model/type/helpers/time_value"
7
- require "active_model/type/helpers/timezone"
@@ -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
- # TODO Change this to private once we've dropped Ruby 2.2 support.
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
@@ -16,8 +16,8 @@ module ActiveModel
16
16
  def cast_value(value)
17
17
  case value
18
18
  when ::String then ::String.new(value)
19
- when true then "t".freeze
20
- when false then "f".freeze
19
+ when true then "t"
20
+ when false then "f"
21
21
  else value.to_s
22
22
  end
23
23
  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.send(:attr_reader, *attr_readers)
58
- klass.send(:attr_writer, *attr_writers)
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
- # In Ruby 2.2 <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
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.send(:attr_reader, *attributes.map do |attribute|
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.send(:attr_writer, *attributes.map do |attribute|
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 :gender, in: %w( m f )
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
- Kernel.Float(raw_value).to_d
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 === raw_value.to_s
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]/ === raw_value.to_s
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 :gender, inclusion: %w(male female)
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?("::".freeze) ? key.constantize : const_get(key)
119
+ validator = key.include?("::") ? key.constantize : const_get(key)
120
120
  rescue NameError
121
121
  raise ArgumentError, "Unknown validator: '#{key}'"
122
122
  end
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/array/extract_options"
4
- require "active_support/core_ext/hash/keys"
5
- require "active_support/core_ext/hash/except"
6
4
 
7
5
  module ActiveModel
8
6
  # == Active \Model \Validations
@@ -90,7 +90,7 @@ module ActiveModel
90
90
  # class MyValidator < ActiveModel::Validator
91
91
  # def initialize(options={})
92
92
  # super
93
- # options[:class].send :attr_accessor, :custom_attribute
93
+ # options[:class].attr_accessor :custom_attribute
94
94
  # end
95
95
  # end
96
96
  class Validator
data/lib/active_model.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #--
4
- # Copyright (c) 2004-2018 David Heinemeier Hansson
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