activemodel 5.2.8.1 → 6.1.6.1

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +91 -97
  3. data/MIT-LICENSE +1 -2
  4. data/README.rdoc +6 -4
  5. data/lib/active_model/attribute/user_provided_default.rb +1 -2
  6. data/lib/active_model/attribute.rb +21 -21
  7. data/lib/active_model/attribute_assignment.rb +4 -6
  8. data/lib/active_model/attribute_methods.rb +117 -40
  9. data/lib/active_model/attribute_mutation_tracker.rb +90 -33
  10. data/lib/active_model/attribute_set/builder.rb +81 -16
  11. data/lib/active_model/attribute_set/yaml_encoder.rb +1 -2
  12. data/lib/active_model/attribute_set.rb +20 -28
  13. data/lib/active_model/attributes.rb +65 -44
  14. data/lib/active_model/callbacks.rb +11 -9
  15. data/lib/active_model/conversion.rb +1 -1
  16. data/lib/active_model/dirty.rb +51 -101
  17. data/lib/active_model/error.rb +207 -0
  18. data/lib/active_model/errors.rb +347 -155
  19. data/lib/active_model/gem_version.rb +3 -3
  20. data/lib/active_model/lint.rb +1 -1
  21. data/lib/active_model/naming.rb +22 -7
  22. data/lib/active_model/nested_error.rb +22 -0
  23. data/lib/active_model/railtie.rb +6 -0
  24. data/lib/active_model/secure_password.rb +55 -55
  25. data/lib/active_model/serialization.rb +9 -7
  26. data/lib/active_model/serializers/json.rb +17 -9
  27. data/lib/active_model/translation.rb +1 -1
  28. data/lib/active_model/type/big_integer.rb +0 -1
  29. data/lib/active_model/type/binary.rb +1 -1
  30. data/lib/active_model/type/boolean.rb +0 -1
  31. data/lib/active_model/type/date.rb +0 -5
  32. data/lib/active_model/type/date_time.rb +3 -8
  33. data/lib/active_model/type/decimal.rb +0 -1
  34. data/lib/active_model/type/float.rb +2 -3
  35. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +14 -6
  36. data/lib/active_model/type/helpers/numeric.rb +17 -6
  37. data/lib/active_model/type/helpers/time_value.rb +37 -15
  38. data/lib/active_model/type/helpers/timezone.rb +1 -1
  39. data/lib/active_model/type/immutable_string.rb +14 -11
  40. data/lib/active_model/type/integer.rb +15 -18
  41. data/lib/active_model/type/registry.rb +17 -19
  42. data/lib/active_model/type/string.rb +12 -3
  43. data/lib/active_model/type/time.rb +1 -6
  44. data/lib/active_model/type/value.rb +9 -2
  45. data/lib/active_model/type.rb +3 -2
  46. data/lib/active_model/validations/absence.rb +2 -2
  47. data/lib/active_model/validations/acceptance.rb +34 -27
  48. data/lib/active_model/validations/callbacks.rb +15 -16
  49. data/lib/active_model/validations/clusivity.rb +6 -3
  50. data/lib/active_model/validations/confirmation.rb +4 -4
  51. data/lib/active_model/validations/exclusion.rb +1 -1
  52. data/lib/active_model/validations/format.rb +2 -3
  53. data/lib/active_model/validations/inclusion.rb +2 -2
  54. data/lib/active_model/validations/length.rb +3 -3
  55. data/lib/active_model/validations/numericality.rb +58 -44
  56. data/lib/active_model/validations/presence.rb +1 -1
  57. data/lib/active_model/validations/validates.rb +7 -6
  58. data/lib/active_model/validations.rb +6 -9
  59. data/lib/active_model/validator.rb +8 -3
  60. data/lib/active_model.rb +2 -1
  61. metadata +13 -7
@@ -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
  #
@@ -4,7 +4,7 @@ module ActiveModel
4
4
  module Validations
5
5
  class PresenceValidator < EachValidator # :nodoc:
6
6
  def validate_each(record, attr_name, value)
7
- record.errors.add(attr_name, :blank, options) if value.blank?
7
+ record.errors.add(attr_name, :blank, **options) if value.blank?
8
8
  end
9
9
  end
10
10
 
@@ -12,6 +12,7 @@ module ActiveModel
12
12
  #
13
13
  # Examples of using the default rails validators:
14
14
  #
15
+ # validates :username, absence: true
15
16
  # validates :terms, acceptance: true
16
17
  # validates :password, confirmation: true
17
18
  # validates :username, exclusion: { in: %w(admin superuser) }
@@ -27,7 +28,7 @@ module ActiveModel
27
28
  # class EmailValidator < ActiveModel::EachValidator
28
29
  # def validate_each(record, attribute, value)
29
30
  # record.errors.add attribute, (options[:message] || "is not an email") unless
30
- # value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
31
+ # /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i.match?(value)
31
32
  # end
32
33
  # end
33
34
  #
@@ -47,7 +48,7 @@ module ActiveModel
47
48
  #
48
49
  # class TitleValidator < ActiveModel::EachValidator
49
50
  # def validate_each(record, attribute, value)
50
- # record.errors.add attribute, "must start with 'the'" unless value =~ /\Athe/i
51
+ # record.errors.add attribute, "must start with 'the'" unless /\Athe/i.match?(value)
51
52
  # end
52
53
  # end
53
54
  #
@@ -63,7 +64,7 @@ module ActiveModel
63
64
  # and strings in shortcut form.
64
65
  #
65
66
  # validates :email, format: /@/
66
- # validates :gender, inclusion: %w(male female)
67
+ # validates :role, inclusion: %w(admin contributor)
67
68
  # validates :password, length: 6..20
68
69
  #
69
70
  # When using shortcut form, ranges and arrays are passed to your
@@ -112,15 +113,16 @@ module ActiveModel
112
113
  defaults[:attributes] = attributes
113
114
 
114
115
  validations.each do |key, options|
115
- next unless options
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
123
123
 
124
+ next unless options
125
+
124
126
  validates_with(validator, defaults.merge(_parse_validates_options(options)))
125
127
  end
126
128
  end
@@ -150,7 +152,6 @@ module ActiveModel
150
152
  end
151
153
 
152
154
  private
153
-
154
155
  # When creating custom validators, it might be useful to be able to specify
155
156
  # additional default keys. This can be done by overwriting this method.
156
157
  def _validates_default_keys
@@ -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?
@@ -85,12 +85,12 @@ module ActiveModel
85
85
  #
86
86
  # It can be useful to access the class that is using that validator when there are prerequisites such
87
87
  # as an +attr_accessor+ being present. This class is accessible via <tt>options[:class]</tt> in the constructor.
88
- # To setup your validator override the constructor.
88
+ # To set up your validator override the constructor.
89
89
  #
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
@@ -149,6 +149,7 @@ module ActiveModel
149
149
  attributes.each do |attribute|
150
150
  value = record.read_attribute_for_validation(attribute)
151
151
  next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
152
+ value = prepare_value_for_validation(value, record, attribute)
152
153
  validate_each(record, attribute, value)
153
154
  end
154
155
  end
@@ -164,6 +165,11 @@ module ActiveModel
164
165
  # +ArgumentError+ when invalid options are supplied.
165
166
  def check_validity!
166
167
  end
168
+
169
+ private
170
+ def prepare_value_for_validation(value, record, attr_name)
171
+ value
172
+ end
167
173
  end
168
174
 
169
175
  # +BlockValidator+ is a special +EachValidator+ which receives a block on initialization
@@ -175,7 +181,6 @@ module ActiveModel
175
181
  end
176
182
 
177
183
  private
178
-
179
184
  def validate_each(record, attribute, value)
180
185
  @block.call(record, attribute, value)
181
186
  end
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-2022 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
@@ -53,6 +53,7 @@ module ActiveModel
53
53
 
54
54
  eager_autoload do
55
55
  autoload :Errors
56
+ autoload :Error
56
57
  autoload :RangeError, "active_model/errors"
57
58
  autoload :StrictValidationFailed, "active_model/errors"
58
59
  autoload :UnknownAttributeError, "active_model/errors"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activemodel
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.8.1
4
+ version: 6.1.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 5.2.8.1
19
+ version: 6.1.6.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 5.2.8.1
26
+ version: 6.1.6.1
27
27
  description: A toolkit for building modeling frameworks like Active Record. Rich support
28
28
  for attributes, callbacks, validations, serialization, internationalization, and
29
29
  testing.
@@ -48,6 +48,7 @@ files:
48
48
  - lib/active_model/callbacks.rb
49
49
  - lib/active_model/conversion.rb
50
50
  - lib/active_model/dirty.rb
51
+ - lib/active_model/error.rb
51
52
  - lib/active_model/errors.rb
52
53
  - lib/active_model/forbidden_attributes_protection.rb
53
54
  - lib/active_model/gem_version.rb
@@ -55,6 +56,7 @@ files:
55
56
  - lib/active_model/locale/en.yml
56
57
  - lib/active_model/model.rb
57
58
  - lib/active_model/naming.rb
59
+ - lib/active_model/nested_error.rb
58
60
  - lib/active_model/railtie.rb
59
61
  - lib/active_model/secure_password.rb
60
62
  - lib/active_model/serialization.rb
@@ -97,12 +99,16 @@ files:
97
99
  - lib/active_model/validations/with.rb
98
100
  - lib/active_model/validator.rb
99
101
  - lib/active_model/version.rb
100
- homepage: http://rubyonrails.org
102
+ homepage: https://rubyonrails.org
101
103
  licenses:
102
104
  - MIT
103
105
  metadata:
104
- source_code_uri: https://github.com/rails/rails/tree/v5.2.8.1/activemodel
105
- changelog_uri: https://github.com/rails/rails/blob/v5.2.8.1/activemodel/CHANGELOG.md
106
+ bug_tracker_uri: https://github.com/rails/rails/issues
107
+ changelog_uri: https://github.com/rails/rails/blob/v6.1.6.1/activemodel/CHANGELOG.md
108
+ documentation_uri: https://api.rubyonrails.org/v6.1.6.1/
109
+ mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
110
+ source_code_uri: https://github.com/rails/rails/tree/v6.1.6.1/activemodel
111
+ rubygems_mfa_required: 'true'
106
112
  post_install_message:
107
113
  rdoc_options: []
108
114
  require_paths:
@@ -111,7 +117,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
111
117
  requirements:
112
118
  - - ">="
113
119
  - !ruby/object:Gem::Version
114
- version: 2.2.2
120
+ version: 2.5.0
115
121
  required_rubygems_version: !ruby/object:Gem::Requirement
116
122
  requirements:
117
123
  - - ">="