activemodel 5.2.6 → 6.1.4

Sign up to get free protection for your applications and to get access to all the features.
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
  #