activemodel 6.0.5.1 → 6.1.7.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +95 -160
  3. data/MIT-LICENSE +1 -2
  4. data/README.rdoc +1 -1
  5. data/lib/active_model/attribute.rb +18 -17
  6. data/lib/active_model/attribute_assignment.rb +3 -4
  7. data/lib/active_model/attribute_methods.rb +74 -38
  8. data/lib/active_model/attribute_mutation_tracker.rb +8 -5
  9. data/lib/active_model/attribute_set/builder.rb +80 -13
  10. data/lib/active_model/attribute_set.rb +18 -16
  11. data/lib/active_model/attributes.rb +20 -24
  12. data/lib/active_model/callbacks.rb +1 -1
  13. data/lib/active_model/dirty.rb +17 -4
  14. data/lib/active_model/error.rb +207 -0
  15. data/lib/active_model/errors.rb +316 -208
  16. data/lib/active_model/gem_version.rb +3 -3
  17. data/lib/active_model/lint.rb +1 -1
  18. data/lib/active_model/naming.rb +2 -2
  19. data/lib/active_model/nested_error.rb +22 -0
  20. data/lib/active_model/railtie.rb +1 -1
  21. data/lib/active_model/secure_password.rb +15 -14
  22. data/lib/active_model/serialization.rb +9 -6
  23. data/lib/active_model/serializers/json.rb +7 -0
  24. data/lib/active_model/type/date_time.rb +2 -2
  25. data/lib/active_model/type/float.rb +2 -0
  26. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +11 -7
  27. data/lib/active_model/type/helpers/numeric.rb +8 -3
  28. data/lib/active_model/type/helpers/time_value.rb +27 -17
  29. data/lib/active_model/type/helpers/timezone.rb +1 -1
  30. data/lib/active_model/type/immutable_string.rb +14 -10
  31. data/lib/active_model/type/integer.rb +11 -2
  32. data/lib/active_model/type/registry.rb +12 -9
  33. data/lib/active_model/type/string.rb +12 -2
  34. data/lib/active_model/type/value.rb +9 -1
  35. data/lib/active_model/type.rb +3 -2
  36. data/lib/active_model/validations/absence.rb +1 -1
  37. data/lib/active_model/validations/acceptance.rb +1 -1
  38. data/lib/active_model/validations/callbacks.rb +15 -15
  39. data/lib/active_model/validations/clusivity.rb +5 -1
  40. data/lib/active_model/validations/confirmation.rb +2 -2
  41. data/lib/active_model/validations/exclusion.rb +1 -1
  42. data/lib/active_model/validations/format.rb +2 -2
  43. data/lib/active_model/validations/inclusion.rb +1 -1
  44. data/lib/active_model/validations/length.rb +2 -2
  45. data/lib/active_model/validations/numericality.rb +54 -41
  46. data/lib/active_model/validations/presence.rb +1 -1
  47. data/lib/active_model/validations/validates.rb +6 -4
  48. data/lib/active_model/validations.rb +6 -6
  49. data/lib/active_model/validator.rb +7 -1
  50. data/lib/active_model.rb +2 -1
  51. metadata +9 -7
@@ -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&.match?(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)
@@ -29,7 +29,7 @@ module ActiveModel
29
29
  end
30
30
 
31
31
  def record_error(record, attribute, name, value)
32
- record.errors.add(attribute, :invalid, options.except(name).merge!(value: value))
32
+ record.errors.add(attribute, :invalid, **options.except(name).merge!(value: value))
33
33
  end
34
34
 
35
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
@@ -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
 
@@ -24,44 +24,24 @@ module ActiveModel
24
24
  end
25
25
  end
26
26
 
27
- def validate_each(record, attr_name, value)
28
- came_from_user = :"#{attr_name}_came_from_user?"
29
-
30
- if record.respond_to?(came_from_user)
31
- if record.public_send(came_from_user)
32
- raw_value = record.read_attribute_before_type_cast(attr_name)
33
- elsif record.respond_to?(:read_attribute)
34
- raw_value = record.read_attribute(attr_name)
35
- end
36
- else
37
- before_type_cast = :"#{attr_name}_before_type_cast"
38
- if record.respond_to?(before_type_cast)
39
- raw_value = record.public_send(before_type_cast)
40
- end
41
- end
42
- raw_value ||= value
43
-
44
- if record_attribute_changed_in_place?(record, attr_name)
45
- raw_value = value
46
- end
47
-
48
- unless is_number?(raw_value)
49
- 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))
50
30
  return
51
31
  end
52
32
 
53
- if allow_only_integer?(record) && !is_integer?(raw_value)
54
- 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))
55
35
  return
56
36
  end
57
37
 
58
- value = parse_as_number(raw_value)
38
+ value = parse_as_number(value, precision, scale)
59
39
 
60
40
  options.slice(*CHECKS.keys).each do |option, option_value|
61
41
  case option
62
42
  when :odd, :even
63
- unless value.to_i.send(CHECKS[option])
64
- 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))
65
45
  end
66
46
  else
67
47
  case option_value
@@ -71,34 +51,44 @@ module ActiveModel
71
51
  option_value = record.send(option_value)
72
52
  end
73
53
 
74
- option_value = parse_as_number(option_value)
54
+ option_value = parse_as_number(option_value, precision, scale)
75
55
 
76
- unless value.send(CHECKS[option], option_value)
77
- 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))
78
58
  end
79
59
  end
80
60
  end
81
61
  end
82
62
 
83
63
  private
84
- def is_number?(raw_value)
85
- !parse_as_number(raw_value).nil?
86
- rescue ArgumentError, TypeError
87
- false
88
- end
89
-
90
- def parse_as_number(raw_value)
64
+ def parse_as_number(raw_value, precision, scale)
91
65
  if raw_value.is_a?(Float)
92
- raw_value.to_d(Float::DIG)
66
+ parse_float(raw_value, precision, scale)
67
+ elsif raw_value.is_a?(BigDecimal)
68
+ round(raw_value, scale)
93
69
  elsif raw_value.is_a?(Numeric)
94
70
  raw_value
95
71
  elsif is_integer?(raw_value)
96
72
  raw_value.to_i
97
73
  elsif !is_hexadecimal_literal?(raw_value)
98
- Kernel.Float(raw_value).to_d
74
+ parse_float(Kernel.Float(raw_value), precision, scale)
99
75
  end
100
76
  end
101
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
+
102
92
  def is_integer?(raw_value)
103
93
  INTEGER_REGEX.match?(raw_value.to_s)
104
94
  end
@@ -124,6 +114,27 @@ module ActiveModel
124
114
  end
125
115
  end
126
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
+
127
138
  def record_attribute_changed_in_place?(record, attr_name)
128
139
  record.respond_to?(:attribute_changed_in_place?) &&
129
140
  record.attribute_changed_in_place?(attr_name.to_s)
@@ -134,7 +145,8 @@ module ActiveModel
134
145
  # Validates whether the value of the specified attribute is numeric by
135
146
  # trying to convert it to a float with Kernel.Float (if <tt>only_integer</tt>
136
147
  # is +false+) or applying it to the regular expression <tt>/\A[\+\-]?\d+\z/</tt>
137
- # (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.
138
150
  #
139
151
  # class Person < ActiveRecord::Base
140
152
  # validates_numericality_of :value, on: :create
@@ -175,6 +187,7 @@ module ActiveModel
175
187
  # * <tt>:less_than</tt>
176
188
  # * <tt>:less_than_or_equal_to</tt>
177
189
  # * <tt>:only_integer</tt>
190
+ # * <tt>:other_than</tt>
178
191
  #
179
192
  # For example:
180
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 :role, inclusion: %(admin contributor)
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,7 +113,6 @@ 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
@@ -121,6 +121,8 @@ module ActiveModel
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
@@ -15,7 +15,7 @@ module ActiveModel
15
15
  # attr_accessor :first_name, :last_name
16
16
  #
17
17
  # validates_each :first_name, :last_name do |record, attr, value|
18
- # 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")
19
19
  # end
20
20
  # end
21
21
  #
@@ -61,7 +61,7 @@ module ActiveModel
61
61
  # attr_accessor :first_name, :last_name
62
62
  #
63
63
  # validates_each :first_name, :last_name, allow_blank: true do |record, attr, value|
64
- # 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")
65
65
  # end
66
66
  # end
67
67
  #
@@ -163,10 +163,10 @@ module ActiveModel
163
163
  if options.key?(:on)
164
164
  options = options.dup
165
165
  options[:on] = Array(options[:on])
166
- options[:if] = Array(options[:if])
167
- options[:if].unshift ->(o) {
168
- !(options[:on] & Array(o.validation_context)).empty?
169
- }
166
+ options[:if] = [
167
+ ->(o) { !(options[:on] & Array(o.validation_context)).empty? },
168
+ *options[:if]
169
+ ]
170
170
  end
171
171
 
172
172
  set_callback(:validate, *args, options, &block)
@@ -85,7 +85,7 @@ 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={})
@@ -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
data/lib/active_model.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #--
4
- # Copyright (c) 2004-2019 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,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activemodel
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.0.5.1
4
+ version: 6.1.7.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-12 00:00:00.000000000 Z
11
+ date: 2023-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 6.0.5.1
19
+ version: 6.1.7.4
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: 6.0.5.1
26
+ version: 6.1.7.4
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
@@ -102,10 +104,10 @@ licenses:
102
104
  - MIT
103
105
  metadata:
104
106
  bug_tracker_uri: https://github.com/rails/rails/issues
105
- changelog_uri: https://github.com/rails/rails/blob/v6.0.5.1/activemodel/CHANGELOG.md
106
- documentation_uri: https://api.rubyonrails.org/v6.0.5.1/
107
+ changelog_uri: https://github.com/rails/rails/blob/v6.1.7.4/activemodel/CHANGELOG.md
108
+ documentation_uri: https://api.rubyonrails.org/v6.1.7.4/
107
109
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
108
- source_code_uri: https://github.com/rails/rails/tree/v6.0.5.1/activemodel
110
+ source_code_uri: https://github.com/rails/rails/tree/v6.1.7.4/activemodel
109
111
  rubygems_mfa_required: 'true'
110
112
  post_install_message:
111
113
  rdoc_options: []