activemodel 5.2.6 → 6.1.4

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +58 -109
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -4
  5. data/lib/active_model.rb +2 -1
  6. data/lib/active_model/attribute.rb +21 -21
  7. data/lib/active_model/attribute/user_provided_default.rb +1 -2
  8. data/lib/active_model/attribute_assignment.rb +4 -6
  9. data/lib/active_model/attribute_methods.rb +117 -40
  10. data/lib/active_model/attribute_mutation_tracker.rb +90 -33
  11. data/lib/active_model/attribute_set.rb +20 -28
  12. data/lib/active_model/attribute_set/builder.rb +81 -16
  13. data/lib/active_model/attribute_set/yaml_encoder.rb +1 -2
  14. data/lib/active_model/attributes.rb +65 -44
  15. data/lib/active_model/callbacks.rb +11 -9
  16. data/lib/active_model/conversion.rb +1 -1
  17. data/lib/active_model/dirty.rb +51 -101
  18. data/lib/active_model/error.rb +207 -0
  19. data/lib/active_model/errors.rb +347 -155
  20. data/lib/active_model/gem_version.rb +3 -3
  21. data/lib/active_model/lint.rb +1 -1
  22. data/lib/active_model/naming.rb +22 -7
  23. data/lib/active_model/nested_error.rb +22 -0
  24. data/lib/active_model/railtie.rb +6 -0
  25. data/lib/active_model/secure_password.rb +54 -55
  26. data/lib/active_model/serialization.rb +9 -7
  27. data/lib/active_model/serializers/json.rb +17 -9
  28. data/lib/active_model/translation.rb +1 -1
  29. data/lib/active_model/type/big_integer.rb +0 -1
  30. data/lib/active_model/type/binary.rb +1 -1
  31. data/lib/active_model/type/boolean.rb +0 -1
  32. data/lib/active_model/type/date.rb +0 -5
  33. data/lib/active_model/type/date_time.rb +3 -8
  34. data/lib/active_model/type/decimal.rb +0 -1
  35. data/lib/active_model/type/float.rb +2 -3
  36. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +14 -6
  37. data/lib/active_model/type/helpers/numeric.rb +17 -6
  38. data/lib/active_model/type/helpers/time_value.rb +37 -15
  39. data/lib/active_model/type/helpers/timezone.rb +1 -1
  40. data/lib/active_model/type/immutable_string.rb +14 -11
  41. data/lib/active_model/type/integer.rb +15 -18
  42. data/lib/active_model/type/registry.rb +16 -16
  43. data/lib/active_model/type/string.rb +12 -3
  44. data/lib/active_model/type/time.rb +1 -6
  45. data/lib/active_model/type/value.rb +9 -2
  46. data/lib/active_model/validations.rb +6 -9
  47. data/lib/active_model/validations/absence.rb +2 -2
  48. data/lib/active_model/validations/acceptance.rb +34 -27
  49. data/lib/active_model/validations/callbacks.rb +15 -16
  50. data/lib/active_model/validations/clusivity.rb +6 -3
  51. data/lib/active_model/validations/confirmation.rb +4 -4
  52. data/lib/active_model/validations/exclusion.rb +1 -1
  53. data/lib/active_model/validations/format.rb +2 -3
  54. data/lib/active_model/validations/inclusion.rb +2 -2
  55. data/lib/active_model/validations/length.rb +3 -3
  56. data/lib/active_model/validations/numericality.rb +58 -44
  57. data/lib/active_model/validations/presence.rb +1 -1
  58. data/lib/active_model/validations/validates.rb +7 -6
  59. data/lib/active_model/validator.rb +8 -3
  60. metadata +14 -9
@@ -11,13 +11,22 @@ module ActiveModel
11
11
  end
12
12
  end
13
13
 
14
- private
14
+ def to_immutable_string
15
+ ImmutableString.new(
16
+ true: @true,
17
+ false: @false,
18
+ limit: limit,
19
+ precision: precision,
20
+ scale: scale,
21
+ )
22
+ end
15
23
 
24
+ private
16
25
  def cast_value(value)
17
26
  case value
18
27
  when ::String then ::String.new(value)
19
- when true then "t".freeze
20
- when false then "f".freeze
28
+ when true then @true
29
+ when false then @false
21
30
  else value.to_s
22
31
  end
23
32
  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?
@@ -11,6 +11,14 @@ module ActiveModel
11
11
  @limit = limit
12
12
  end
13
13
 
14
+ # Returns true if this type can convert +value+ to a type that is usable
15
+ # by the database. For example a boolean type can return +true+ if the
16
+ # value parameter is a Ruby boolean, but may return +false+ if the value
17
+ # parameter is some other object.
18
+ def serializable?(value)
19
+ true
20
+ end
21
+
14
22
  def type # :nodoc:
15
23
  end
16
24
 
@@ -110,11 +118,10 @@ module ActiveModel
110
118
  [self.class, precision, scale, limit].hash
111
119
  end
112
120
 
113
- def assert_valid_value(*)
121
+ def assert_valid_value(_)
114
122
  end
115
123
 
116
124
  private
117
-
118
125
  # Convenience method for types which do not need separate type casting
119
126
  # behavior for user and database inputs. Called by Value#cast for
120
127
  # values except +nil+.
@@ -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
@@ -17,7 +15,7 @@ module ActiveModel
17
15
  # attr_accessor :first_name, :last_name
18
16
  #
19
17
  # validates_each :first_name, :last_name do |record, attr, value|
20
- # record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
18
+ # record.errors.add attr, "starts with z." if value.start_with?("z")
21
19
  # end
22
20
  # end
23
21
  #
@@ -63,7 +61,7 @@ module ActiveModel
63
61
  # attr_accessor :first_name, :last_name
64
62
  #
65
63
  # validates_each :first_name, :last_name, allow_blank: true do |record, attr, value|
66
- # record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
64
+ # record.errors.add attr, "starts with z." if value.start_with?("z")
67
65
  # end
68
66
  # end
69
67
  #
@@ -165,10 +163,10 @@ module ActiveModel
165
163
  if options.key?(:on)
166
164
  options = options.dup
167
165
  options[:on] = Array(options[:on])
168
- options[:if] = Array(options[:if])
169
- options[:if].unshift ->(o) {
170
- !(options[:on] & Array(o.validation_context)).empty?
171
- }
166
+ options[:if] = [
167
+ ->(o) { !(options[:on] & Array(o.validation_context)).empty? },
168
+ *options[:if]
169
+ ]
172
170
  end
173
171
 
174
172
  set_callback(:validate, *args, options, &block)
@@ -404,7 +402,6 @@ module ActiveModel
404
402
  alias :read_attribute_for_validation :send
405
403
 
406
404
  private
407
-
408
405
  def run_validations!
409
406
  _run_validate_callbacks
410
407
  errors.empty?
@@ -5,13 +5,13 @@ module ActiveModel
5
5
  # == \Active \Model Absence Validator
6
6
  class AbsenceValidator < EachValidator #:nodoc:
7
7
  def validate_each(record, attr_name, value)
8
- record.errors.add(attr_name, :present, options) if value.present?
8
+ record.errors.add(attr_name, :present, **options) if value.present?
9
9
  end
10
10
  end
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
@@ -10,14 +10,14 @@ module ActiveModel
10
10
 
11
11
  def validate_each(record, attribute, value)
12
12
  unless acceptable_option?(value)
13
- record.errors.add(attribute, :accepted, options.except(:accept, :allow_nil))
13
+ record.errors.add(attribute, :accepted, **options.except(:accept, :allow_nil))
14
14
  end
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
 
@@ -56,14 +56,7 @@ module ActiveModel
56
56
  def before_validation(*args, &block)
57
57
  options = args.extract_options!
58
58
 
59
- if options.key?(:on)
60
- options = options.dup
61
- options[:on] = Array(options[:on])
62
- options[:if] = Array(options[:if])
63
- options[:if].unshift ->(o) {
64
- !(options[:on] & Array(o.validation_context)).empty?
65
- }
66
- end
59
+ set_options_for_callback(options)
67
60
 
68
61
  set_callback(:validation, :before, *args, options, &block)
69
62
  end
@@ -99,20 +92,26 @@ module ActiveModel
99
92
  options = options.dup
100
93
  options[:prepend] = true
101
94
 
102
- if options.key?(:on)
103
- options[:on] = Array(options[:on])
104
- options[:if] = Array(options[:if])
105
- options[:if].unshift ->(o) {
106
- !(options[:on] & Array(o.validation_context)).empty?
107
- }
108
- end
95
+ set_options_for_callback(options)
109
96
 
110
97
  set_callback(:validation, :after, *args, options, &block)
111
98
  end
99
+
100
+ private
101
+ def set_options_for_callback(options)
102
+ if options.key?(:on)
103
+ options[:on] = Array(options[:on])
104
+ options[:if] = [
105
+ ->(o) {
106
+ !(options[:on] & Array(o.validation_context)).empty?
107
+ },
108
+ *options[:if]
109
+ ]
110
+ end
111
+ end
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)
@@ -25,14 +24,18 @@ module ActiveModel
25
24
  delimiter
26
25
  end
27
26
 
28
- members.send(inclusion_method(members), value)
27
+ if value.is_a?(Array)
28
+ value.all? { |v| members.public_send(inclusion_method(members), v) }
29
+ else
30
+ members.public_send(inclusion_method(members), value)
31
+ end
29
32
  end
30
33
 
31
34
  def delimiter
32
35
  @delimiter ||= options[:in] || options[:within]
33
36
  end
34
37
 
35
- # In Ruby 2.2 <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
38
+ # After Ruby 2.2, <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
36
39
  # possible values in the range for equality, which is slower but more accurate.
37
40
  # <tt>Range#cover?</tt> uses the previous logic of comparing a value with the range
38
41
  # endpoints, which is fast but is only accurate on Numeric, Time, Date,
@@ -9,21 +9,21 @@ module ActiveModel
9
9
  end
10
10
 
11
11
  def validate_each(record, attribute, value)
12
- unless (confirmed = record.send("#{attribute}_confirmation")).nil?
12
+ unless (confirmed = record.public_send("#{attribute}_confirmation")).nil?
13
13
  unless confirmation_value_equal?(record, attribute, value, confirmed)
14
14
  human_attribute_name = record.class.human_attribute_name(attribute)
15
- record.errors.add(:"#{attribute}_confirmation", :confirmation, options.except(:case_sensitive).merge!(attribute: human_attribute_name))
15
+ record.errors.add(:"#{attribute}_confirmation", :confirmation, **options.except(:case_sensitive).merge!(attribute: human_attribute_name))
16
16
  end
17
17
  end
18
18
  end
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
@@ -9,7 +9,7 @@ module ActiveModel
9
9
 
10
10
  def validate_each(record, attribute, value)
11
11
  if include?(record, value)
12
- record.errors.add(attribute, :exclusion, options.except(:in, :within).merge!(value: value))
12
+ record.errors.add(attribute, :exclusion, **options.except(:in, :within).merge!(value: value))
13
13
  end
14
14
  end
15
15
  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) unless regexp.match?(value.to_s)
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,14 +23,13 @@ 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
30
29
  end
31
30
 
32
31
  def record_error(record, attribute, name, value)
33
- record.errors.add(attribute, :invalid, options.except(name).merge!(value: value))
32
+ record.errors.add(attribute, :invalid, **options.except(name).merge!(value: value))
34
33
  end
35
34
 
36
35
  def check_options_validity(name)
@@ -9,7 +9,7 @@ module ActiveModel
9
9
 
10
10
  def validate_each(record, attribute, value)
11
11
  unless include?(record, value)
12
- record.errors.add(attribute, :inclusion, options.except(:in, :within).merge!(value: value))
12
+ record.errors.add(attribute, :inclusion, **options.except(:in, :within).merge!(value: value))
13
13
  end
14
14
  end
15
15
  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] }
@@ -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
@@ -51,7 +51,7 @@ module ActiveModel
51
51
  when Symbol
52
52
  check_value = record.send(check_value)
53
53
  end
54
- next if value_length.send(validity_check, check_value)
54
+ next if value_length.public_send(validity_check, check_value)
55
55
  end
56
56
 
57
57
  errors_options[:count] = check_value
@@ -59,7 +59,7 @@ module ActiveModel
59
59
  default_message = options[MESSAGES[key]]
60
60
  errors_options[:message] ||= default_message if default_message
61
61
 
62
- record.errors.add(attribute, MESSAGES[key], errors_options)
62
+ record.errors.add(attribute, MESSAGES[key], **errors_options)
63
63
  end
64
64
  end
65
65
 
@@ -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|
@@ -22,44 +24,24 @@ module ActiveModel
22
24
  end
23
25
  end
24
26
 
25
- def validate_each(record, attr_name, value)
26
- came_from_user = :"#{attr_name}_came_from_user?"
27
-
28
- if record.respond_to?(came_from_user)
29
- if record.public_send(came_from_user)
30
- raw_value = record.read_attribute_before_type_cast(attr_name)
31
- elsif record.respond_to?(:read_attribute)
32
- raw_value = record.read_attribute(attr_name)
33
- end
34
- else
35
- before_type_cast = :"#{attr_name}_before_type_cast"
36
- if record.respond_to?(before_type_cast)
37
- raw_value = record.public_send(before_type_cast)
38
- end
39
- end
40
- raw_value ||= value
41
-
42
- if record_attribute_changed_in_place?(record, attr_name)
43
- raw_value = value
44
- end
45
-
46
- unless is_number?(raw_value)
47
- record.errors.add(attr_name, :not_a_number, filtered_options(raw_value))
27
+ def validate_each(record, attr_name, value, precision: Float::DIG, scale: nil)
28
+ unless is_number?(value, precision, scale)
29
+ record.errors.add(attr_name, :not_a_number, **filtered_options(value))
48
30
  return
49
31
  end
50
32
 
51
- if allow_only_integer?(record) && !is_integer?(raw_value)
52
- record.errors.add(attr_name, :not_an_integer, filtered_options(raw_value))
33
+ if allow_only_integer?(record) && !is_integer?(value)
34
+ record.errors.add(attr_name, :not_an_integer, **filtered_options(value))
53
35
  return
54
36
  end
55
37
 
56
- value = parse_as_number(raw_value)
38
+ value = parse_as_number(value, precision, scale)
57
39
 
58
40
  options.slice(*CHECKS.keys).each do |option, option_value|
59
41
  case option
60
42
  when :odd, :even
61
- unless value.to_i.send(CHECKS[option])
62
- record.errors.add(attr_name, option, filtered_options(value))
43
+ unless value.to_i.public_send(CHECKS[option])
44
+ record.errors.add(attr_name, option, **filtered_options(value))
63
45
  end
64
46
  else
65
47
  case option_value
@@ -69,41 +51,50 @@ module ActiveModel
69
51
  option_value = record.send(option_value)
70
52
  end
71
53
 
72
- option_value = parse_as_number(option_value)
54
+ option_value = parse_as_number(option_value, precision, scale)
73
55
 
74
- unless value.send(CHECKS[option], option_value)
75
- record.errors.add(attr_name, option, filtered_options(value).merge!(count: option_value))
56
+ unless value.public_send(CHECKS[option], option_value)
57
+ record.errors.add(attr_name, option, **filtered_options(value).merge!(count: option_value))
76
58
  end
77
59
  end
78
60
  end
79
61
  end
80
62
 
81
63
  private
82
-
83
- def is_number?(raw_value)
84
- !parse_as_number(raw_value).nil?
85
- rescue ArgumentError, TypeError
86
- false
87
- end
88
-
89
- def parse_as_number(raw_value)
64
+ def parse_as_number(raw_value, precision, scale)
90
65
  if raw_value.is_a?(Float)
91
- raw_value.to_d
66
+ parse_float(raw_value, precision, scale)
67
+ elsif raw_value.is_a?(BigDecimal)
68
+ round(raw_value, scale)
92
69
  elsif raw_value.is_a?(Numeric)
93
70
  raw_value
94
71
  elsif is_integer?(raw_value)
95
72
  raw_value.to_i
96
73
  elsif !is_hexadecimal_literal?(raw_value)
97
- Kernel.Float(raw_value).to_d
74
+ parse_float(Kernel.Float(raw_value), precision, scale)
98
75
  end
99
76
  end
100
77
 
78
+ def parse_float(raw_value, precision, scale)
79
+ round(raw_value, scale).to_d(precision)
80
+ end
81
+
82
+ def round(raw_value, scale)
83
+ scale ? raw_value.round(scale) : raw_value
84
+ end
85
+
86
+ def is_number?(raw_value, precision, scale)
87
+ !parse_as_number(raw_value, precision, scale).nil?
88
+ rescue ArgumentError, TypeError
89
+ false
90
+ end
91
+
101
92
  def is_integer?(raw_value)
102
- INTEGER_REGEX === raw_value.to_s
93
+ INTEGER_REGEX.match?(raw_value.to_s)
103
94
  end
104
95
 
105
96
  def is_hexadecimal_literal?(raw_value)
106
- /\A0[xX]/ === raw_value.to_s
97
+ HEXADECIMAL_REGEX.match?(raw_value.to_s)
107
98
  end
108
99
 
109
100
  def filtered_options(value)
@@ -123,6 +114,27 @@ module ActiveModel
123
114
  end
124
115
  end
125
116
 
117
+ def prepare_value_for_validation(value, record, attr_name)
118
+ return value if record_attribute_changed_in_place?(record, attr_name)
119
+
120
+ came_from_user = :"#{attr_name}_came_from_user?"
121
+
122
+ if record.respond_to?(came_from_user)
123
+ if record.public_send(came_from_user)
124
+ raw_value = record.public_send(:"#{attr_name}_before_type_cast")
125
+ elsif record.respond_to?(:read_attribute)
126
+ raw_value = record.read_attribute(attr_name)
127
+ end
128
+ else
129
+ before_type_cast = :"#{attr_name}_before_type_cast"
130
+ if record.respond_to?(before_type_cast)
131
+ raw_value = record.public_send(before_type_cast)
132
+ end
133
+ end
134
+
135
+ raw_value || value
136
+ end
137
+
126
138
  def record_attribute_changed_in_place?(record, attr_name)
127
139
  record.respond_to?(:attribute_changed_in_place?) &&
128
140
  record.attribute_changed_in_place?(attr_name.to_s)
@@ -133,7 +145,8 @@ module ActiveModel
133
145
  # Validates whether the value of the specified attribute is numeric by
134
146
  # trying to convert it to a float with Kernel.Float (if <tt>only_integer</tt>
135
147
  # is +false+) or applying it to the regular expression <tt>/\A[\+\-]?\d+\z/</tt>
136
- # (if <tt>only_integer</tt> is set to +true+).
148
+ # (if <tt>only_integer</tt> is set to +true+). Precision of Kernel.Float values
149
+ # are guaranteed up to 15 digits.
137
150
  #
138
151
  # class Person < ActiveRecord::Base
139
152
  # validates_numericality_of :value, on: :create
@@ -174,6 +187,7 @@ module ActiveModel
174
187
  # * <tt>:less_than</tt>
175
188
  # * <tt>:less_than_or_equal_to</tt>
176
189
  # * <tt>:only_integer</tt>
190
+ # * <tt>:other_than</tt>
177
191
  #
178
192
  # For example:
179
193
  #