activemodel 5.2.5 → 6.0.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +185 -71
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -3
  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 -2
  8. data/lib/active_model/attribute_methods.rb +56 -15
  9. data/lib/active_model/attribute_mutation_tracker.rb +88 -34
  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 -12
  13. data/lib/active_model/attributes.rb +60 -35
  14. data/lib/active_model/callbacks.rb +10 -8
  15. data/lib/active_model/conversion.rb +1 -1
  16. data/lib/active_model/dirty.rb +36 -99
  17. data/lib/active_model/errors.rb +105 -21
  18. data/lib/active_model/gem_version.rb +4 -4
  19. data/lib/active_model/naming.rb +20 -5
  20. data/lib/active_model/railtie.rb +6 -0
  21. data/lib/active_model/secure_password.rb +47 -48
  22. data/lib/active_model/serialization.rb +0 -1
  23. data/lib/active_model/serializers/json.rb +10 -9
  24. data/lib/active_model/translation.rb +1 -1
  25. data/lib/active_model/type/big_integer.rb +0 -1
  26. data/lib/active_model/type/binary.rb +1 -1
  27. data/lib/active_model/type/boolean.rb +0 -1
  28. data/lib/active_model/type/date.rb +0 -5
  29. data/lib/active_model/type/date_time.rb +3 -8
  30. data/lib/active_model/type/decimal.rb +0 -1
  31. data/lib/active_model/type/float.rb +0 -3
  32. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +4 -0
  33. data/lib/active_model/type/helpers/numeric.rb +9 -3
  34. data/lib/active_model/type/helpers/time_value.rb +17 -5
  35. data/lib/active_model/type/immutable_string.rb +0 -1
  36. data/lib/active_model/type/integer.rb +8 -20
  37. data/lib/active_model/type/registry.rb +11 -16
  38. data/lib/active_model/type/string.rb +2 -3
  39. data/lib/active_model/type/time.rb +1 -6
  40. data/lib/active_model/type/value.rb +0 -1
  41. data/lib/active_model/validations/absence.rb +1 -1
  42. data/lib/active_model/validations/acceptance.rb +33 -26
  43. data/lib/active_model/validations/callbacks.rb +0 -1
  44. data/lib/active_model/validations/clusivity.rb +1 -2
  45. data/lib/active_model/validations/confirmation.rb +2 -2
  46. data/lib/active_model/validations/format.rb +1 -2
  47. data/lib/active_model/validations/inclusion.rb +1 -1
  48. data/lib/active_model/validations/length.rb +1 -1
  49. data/lib/active_model/validations/numericality.rb +5 -4
  50. data/lib/active_model/validations/validates.rb +2 -3
  51. data/lib/active_model/validations.rb +0 -3
  52. data/lib/active_model/validator.rb +1 -2
  53. data/lib/active_model.rb +1 -1
  54. metadata +15 -12
@@ -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
 
@@ -64,7 +64,7 @@ module ActiveModel
64
64
  defaults << attribute.humanize
65
65
 
66
66
  options[:default] = defaults
67
- I18n.translate(defaults.shift, options)
67
+ I18n.translate(defaults.shift, **options)
68
68
  end
69
69
  end
70
70
  end
@@ -6,7 +6,6 @@ module ActiveModel
6
6
  module Type
7
7
  class BigInteger < Integer # :nodoc:
8
8
  private
9
-
10
9
  def max_value
11
10
  ::Float::INFINITY
12
11
  end
@@ -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)
@@ -34,7 +34,6 @@ module ActiveModel
34
34
  end
35
35
 
36
36
  private
37
-
38
37
  def cast_value(value)
39
38
  if value == ""
40
39
  nil
@@ -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
- missing_parameter = (1..3).detect { |key| !values_hash.key?(key) }
44
- if missing_parameter
45
- raise ArgumentError, missing_parameter
38
+ missing_parameters = (1..3).select { |key| !values_hash.key?(key) }
39
+ if missing_parameters.any?
40
+ raise ArgumentError, "Provided hash #{values_hash} doesn't contain necessary keys: #{missing_parameters}"
46
41
  end
47
42
  super
48
43
  end
@@ -17,7 +17,6 @@ module ActiveModel
17
17
  end
18
18
 
19
19
  private
20
-
21
20
  def cast_value(value)
22
21
  casted_value = \
23
22
  case value
@@ -18,10 +18,7 @@ module ActiveModel
18
18
  end
19
19
  end
20
20
 
21
- alias serialize cast
22
-
23
21
  private
24
-
25
22
  def cast_value(value)
26
23
  case value
27
24
  when ::Float then value
@@ -5,6 +5,10 @@ module ActiveModel
5
5
  module Helpers # :nodoc: all
6
6
  class AcceptsMultiparameterTime < Module
7
7
  def initialize(defaults: {})
8
+ define_method(:serialize) do |value|
9
+ super(cast(value))
10
+ end
11
+
8
12
  define_method(:cast) do |value|
9
13
  if value.is_a?(Hash)
10
14
  value_from_multiparameter_assignment(value)
@@ -4,6 +4,10 @@ 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
12
  value = \
9
13
  case value
@@ -20,17 +24,19 @@ module ActiveModel
20
24
  end
21
25
 
22
26
  private
23
-
24
27
  def number_to_non_number?(old_value, new_value_before_type_cast)
25
- old_value != nil && non_numeric_string?(new_value_before_type_cast)
28
+ old_value != nil && non_numeric_string?(new_value_before_type_cast.to_s)
26
29
  end
27
30
 
28
31
  def non_numeric_string?(value)
29
32
  # 'wibble'.to_i will give zero, we want to make sure
30
33
  # that we aren't marking int zero to string zero as
31
34
  # changed.
32
- !/\A[-+]?\d+/.match?(value.to_s)
35
+ !NUMERIC_REGEX.match?(value)
33
36
  end
37
+
38
+ NUMERIC_REGEX = /\A\s*[+-]?\d/
39
+ private_constant :NUMERIC_REGEX
34
40
  end
35
41
  end
36
42
  end
@@ -22,10 +22,17 @@ module ActiveModel
22
22
  end
23
23
 
24
24
  def apply_seconds_precision(value)
25
- return value unless precision && value.respond_to?(:usec)
26
- number_of_insignificant_digits = 6 - precision
25
+ return value unless precision && value.respond_to?(:nsec)
26
+
27
+ number_of_insignificant_digits = 9 - precision
27
28
  round_power = 10**number_of_insignificant_digits
28
- value.change(usec: value.usec - value.usec % round_power)
29
+ rounded_off_nsec = value.nsec % round_power
30
+
31
+ if rounded_off_nsec > 0
32
+ value.change(nsec: value.nsec - rounded_off_nsec)
33
+ else
34
+ value
35
+ end
29
36
  end
30
37
 
31
38
  def type_cast_for_schema(value)
@@ -37,7 +44,6 @@ module ActiveModel
37
44
  end
38
45
 
39
46
  private
40
-
41
47
  def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
42
48
  # Treat 0000-00-00 00:00:00 as nil.
43
49
  return if year.nil? || (year == 0 && mon == 0 && mday == 0)
@@ -58,7 +64,13 @@ module ActiveModel
58
64
  # Doesn't handle time zones.
59
65
  def fast_string_to_time(string)
60
66
  if string =~ ISO_DATETIME
61
- microsec = ($7.to_r * 1_000_000).to_i
67
+ microsec_part = $7
68
+ if microsec_part && microsec_part.start_with?(".") && microsec_part.length == 7
69
+ microsec_part[0] = ""
70
+ microsec = microsec_part.to_i
71
+ else
72
+ microsec = (microsec_part.to_r * 1_000_000).to_i
73
+ end
62
74
  new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
63
75
  end
64
76
  end
@@ -17,7 +17,6 @@ module ActiveModel
17
17
  end
18
18
 
19
19
  private
20
-
21
20
  def cast_value(value)
22
21
  result = \
23
22
  case value
@@ -9,7 +9,7 @@ module ActiveModel
9
9
  # 4 bytes means an integer as opposed to smallint etc.
10
10
  DEFAULT_LIMIT = 4
11
11
 
12
- def initialize(*)
12
+ def initialize(*, **)
13
13
  super
14
14
  @range = min_value...max_value
15
15
  end
@@ -19,39 +19,27 @@ module ActiveModel
19
19
  end
20
20
 
21
21
  def deserialize(value)
22
- return if value.nil?
22
+ return if value.blank?
23
23
  value.to_i
24
24
  end
25
25
 
26
26
  def serialize(value)
27
- result = cast(value)
28
- if result
29
- ensure_in_range(result)
30
- end
31
- result
27
+ return if value.is_a?(::String) && non_numeric_string?(value)
28
+ ensure_in_range(super)
32
29
  end
33
30
 
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
31
  private
32
+ attr_reader :range
41
33
 
42
34
  def cast_value(value)
43
- case value
44
- when true then 1
45
- when false then 0
46
- else
47
- value.to_i rescue nil
48
- end
35
+ value.to_i rescue nil
49
36
  end
50
37
 
51
38
  def ensure_in_range(value)
52
- unless range.cover?(value)
39
+ if value && !range.cover?(value)
53
40
  raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit} bytes"
54
41
  end
42
+ value
55
43
  end
56
44
 
57
45
  def max_value
@@ -9,34 +9,32 @@ module ActiveModel
9
9
  end
10
10
 
11
11
  def register(type_name, klass = nil, **options, &block)
12
- block ||= proc { |_, *args| klass.new(*args) }
12
+ unless block_given?
13
+ block = proc { |_, *args| klass.new(*args) }
14
+ block.ruby2_keywords if block.respond_to?(:ruby2_keywords)
15
+ end
13
16
  registrations << registration_klass.new(type_name, block, **options)
14
17
  end
15
18
 
16
- def lookup(symbol, *args)
17
- registration = find_registration(symbol, *args)
19
+ def lookup(symbol, *args, **kwargs)
20
+ registration = find_registration(symbol, *args, **kwargs)
18
21
 
19
22
  if registration
20
- registration.call(self, symbol, *args)
23
+ registration.call(self, symbol, *args, **kwargs)
21
24
  else
22
25
  raise ArgumentError, "Unknown type #{symbol.inspect}"
23
26
  end
24
27
  end
25
28
 
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
29
  private
30
+ attr_reader :registrations
33
31
 
34
32
  def registration_klass
35
33
  Registration
36
34
  end
37
35
 
38
- def find_registration(symbol, *args)
39
- registrations.find { |r| r.matches?(symbol, *args) }
36
+ def find_registration(symbol, *args, **kwargs)
37
+ registrations.find { |r| r.matches?(symbol, *args, **kwargs) }
40
38
  end
41
39
  end
42
40
 
@@ -59,10 +57,7 @@ module ActiveModel
59
57
  type_name == name
60
58
  end
61
59
 
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
-
60
+ private
66
61
  attr_reader :name, :block
67
62
  end
68
63
  end
@@ -12,12 +12,11 @@ module ActiveModel
12
12
  end
13
13
 
14
14
  private
15
-
16
15
  def cast_value(value)
17
16
  case value
18
17
  when ::String then ::String.new(value)
19
- when true then "t".freeze
20
- when false then "f".freeze
18
+ when true then "t"
19
+ when false then "f"
21
20
  else value.to_s
22
21
  end
23
22
  end
@@ -6,17 +6,13 @@ module ActiveModel
6
6
  include Helpers::Timezone
7
7
  include Helpers::TimeValue
8
8
  include Helpers::AcceptsMultiparameterTime.new(
9
- defaults: { 1 => 1970, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
9
+ defaults: { 1 => 2000, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
10
10
  )
11
11
 
12
12
  def type
13
13
  :time
14
14
  end
15
15
 
16
- def serialize(value)
17
- super(cast(value))
18
- end
19
-
20
16
  def user_input_in_time_zone(value)
21
17
  return unless value.present?
22
18
 
@@ -33,7 +29,6 @@ module ActiveModel
33
29
  end
34
30
 
35
31
  private
36
-
37
32
  def cast_value(value)
38
33
  return apply_seconds_precision(value) unless value.is_a?(::String)
39
34
  return if value.empty?
@@ -114,7 +114,6 @@ module ActiveModel
114
114
  end
115
115
 
116
116
  private
117
-
118
117
  # Convenience method for types which do not need separate type casting
119
118
  # behavior for user and database inputs. Called by Value#cast for
120
119
  # values except +nil+.
@@ -11,7 +11,7 @@ module ActiveModel
11
11
 
12
12
  module HelperMethods
13
13
  # Validates that the specified attributes are blank (as defined by
14
- # Object#blank?). Happens by default on save.
14
+ # Object#present?). Happens by default on save.
15
15
  #
16
16
  # class Person < ActiveRecord::Base
17
17
  # validates_absence_of :first_name
@@ -15,9 +15,9 @@ module ActiveModel
15
15
  end
16
16
 
17
17
  private
18
-
19
18
  def setup!(klass)
20
- klass.include(LazilyDefineAttributes.new(AttributeDefinition.new(attributes)))
19
+ define_attributes = LazilyDefineAttributes.new(attributes)
20
+ klass.include(define_attributes) unless klass.included_modules.include?(define_attributes)
21
21
  end
22
22
 
23
23
  def acceptable_option?(value)
@@ -25,50 +25,57 @@ module ActiveModel
25
25
  end
26
26
 
27
27
  class LazilyDefineAttributes < Module
28
- def initialize(attribute_definition)
28
+ def initialize(attributes)
29
+ @attributes = attributes.map(&:to_s)
30
+ end
31
+
32
+ def included(klass)
33
+ @lock = Mutex.new
34
+ mod = self
35
+
29
36
  define_method(:respond_to_missing?) do |method_name, include_private = false|
30
- super(method_name, include_private) || attribute_definition.matches?(method_name)
37
+ mod.define_on(klass)
38
+ super(method_name, include_private) || mod.matches?(method_name)
31
39
  end
32
40
 
33
41
  define_method(:method_missing) do |method_name, *args, &block|
34
- if attribute_definition.matches?(method_name)
35
- attribute_definition.define_on(self.class)
42
+ mod.define_on(klass)
43
+ if mod.matches?(method_name)
36
44
  send(method_name, *args, &block)
37
45
  else
38
46
  super(method_name, *args, &block)
39
47
  end
40
48
  end
41
49
  end
42
- end
43
-
44
- class AttributeDefinition
45
- def initialize(attributes)
46
- @attributes = attributes.map(&:to_s)
47
- end
48
50
 
49
51
  def matches?(method_name)
50
- attr_name = convert_to_reader_name(method_name)
51
- attributes.include?(attr_name)
52
+ attr_name = method_name.to_s.chomp("=")
53
+ attributes.any? { |name| name == attr_name }
52
54
  end
53
55
 
54
56
  def define_on(klass)
55
- attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
56
- attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
57
- klass.send(:attr_reader, *attr_readers)
58
- klass.send(:attr_writer, *attr_writers)
59
- end
57
+ @lock&.synchronize do
58
+ return unless @lock
60
59
 
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
60
+ attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
61
+ attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
64
62
 
65
- attr_reader :attributes
63
+ attr_reader(*attr_readers)
64
+ attr_writer(*attr_writers)
66
65
 
67
- private
66
+ remove_method :respond_to_missing?
67
+ remove_method :method_missing
68
68
 
69
- def convert_to_reader_name(method_name)
70
- method_name.to_s.chomp("=")
69
+ @lock = nil
71
70
  end
71
+ end
72
+
73
+ def ==(other)
74
+ self.class == other.class && attributes == other.attributes
75
+ end
76
+
77
+ protected
78
+ attr_reader :attributes
72
79
  end
73
80
  end
74
81
 
@@ -112,7 +112,6 @@ module ActiveModel
112
112
  end
113
113
 
114
114
  private
115
-
116
115
  # Overwrite run validations to include callbacks.
117
116
  def run_validations!
118
117
  _run_validation_callbacks { super }
@@ -15,7 +15,6 @@ module ActiveModel
15
15
  end
16
16
 
17
17
  private
18
-
19
18
  def include?(record, value)
20
19
  members = if delimiter.respond_to?(:call)
21
20
  delimiter.call(record)
@@ -32,7 +31,7 @@ module ActiveModel
32
31
  @delimiter ||= options[:in] || options[:within]
33
32
  end
34
33
 
35
- # In Ruby 2.2 <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
34
+ # After Ruby 2.2, <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
36
35
  # possible values in the range for equality, which is slower but more accurate.
37
36
  # <tt>Range#cover?</tt> uses the previous logic of comparing a value with the range
38
37
  # 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
@@ -6,7 +6,7 @@ module ActiveModel
6
6
  def validate_each(record, attribute, value)
7
7
  if options[:with]
8
8
  regexp = option_call(record, :with)
9
- record_error(record, attribute, :with, value) if value.to_s !~ regexp
9
+ record_error(record, attribute, :with, value) if !value.to_s&.match?(regexp)
10
10
  elsif options[:without]
11
11
  regexp = option_call(record, :without)
12
12
  record_error(record, attribute, :without, value) if regexp.match?(value.to_s)
@@ -23,7 +23,6 @@ module ActiveModel
23
23
  end
24
24
 
25
25
  private
26
-
27
26
  def option_call(record, name)
28
27
  option = options[name]
29
28
  option.respond_to?(:call) ? option.call(record) : option
@@ -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] }
@@ -32,7 +32,7 @@ module ActiveModel
32
32
  value = options[key]
33
33
 
34
34
  unless (value.is_a?(Integer) && value >= 0) || value == Float::INFINITY || value.is_a?(Symbol) || value.is_a?(Proc)
35
- raise ArgumentError, ":#{key} must be a nonnegative Integer, Infinity, Symbol, or Proc"
35
+ raise ArgumentError, ":#{key} must be a non-negative Integer, Infinity, Symbol, or Proc"
36
36
  end
37
37
  end
38
38
  end
@@ -13,6 +13,8 @@ module ActiveModel
13
13
 
14
14
  INTEGER_REGEX = /\A[+-]?\d+\z/
15
15
 
16
+ HEXADECIMAL_REGEX = /\A[+-]?0[xX]/
17
+
16
18
  def check_validity!
17
19
  keys = CHECKS.keys - [:odd, :even]
18
20
  options.slice(*keys).each do |option, value|
@@ -79,7 +81,6 @@ module ActiveModel
79
81
  end
80
82
 
81
83
  private
82
-
83
84
  def is_number?(raw_value)
84
85
  !parse_as_number(raw_value).nil?
85
86
  rescue ArgumentError, TypeError
@@ -88,7 +89,7 @@ module ActiveModel
88
89
 
89
90
  def parse_as_number(raw_value)
90
91
  if raw_value.is_a?(Float)
91
- raw_value.to_d
92
+ raw_value.to_d(Float::DIG)
92
93
  elsif raw_value.is_a?(Numeric)
93
94
  raw_value
94
95
  elsif is_integer?(raw_value)
@@ -99,11 +100,11 @@ module ActiveModel
99
100
  end
100
101
 
101
102
  def is_integer?(raw_value)
102
- INTEGER_REGEX === raw_value.to_s
103
+ INTEGER_REGEX.match?(raw_value.to_s)
103
104
  end
104
105
 
105
106
  def is_hexadecimal_literal?(raw_value)
106
- /\A0[xX]/ === raw_value.to_s
107
+ HEXADECIMAL_REGEX.match?(raw_value.to_s)
107
108
  end
108
109
 
109
110
  def filtered_options(value)