activemodel 5.2.8.1 → 6.1.6.1

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