activemodel 5.2.7.1 → 6.0.0.beta1

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.
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