activemodel 6.0.3.2 → 6.1.0

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +48 -182
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/active_model.rb +2 -1
  6. data/lib/active_model/attribute.rb +15 -14
  7. data/lib/active_model/attribute_assignment.rb +3 -4
  8. data/lib/active_model/attribute_methods.rb +74 -38
  9. data/lib/active_model/attribute_mutation_tracker.rb +8 -5
  10. data/lib/active_model/attribute_set.rb +18 -16
  11. data/lib/active_model/attribute_set/builder.rb +80 -13
  12. data/lib/active_model/attributes.rb +20 -24
  13. data/lib/active_model/dirty.rb +12 -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 +14 -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 +11 -4
  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/validations.rb +6 -6
  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/clusivity.rb +5 -1
  39. data/lib/active_model/validations/confirmation.rb +2 -2
  40. data/lib/active_model/validations/exclusion.rb +1 -1
  41. data/lib/active_model/validations/format.rb +2 -2
  42. data/lib/active_model/validations/inclusion.rb +1 -1
  43. data/lib/active_model/validations/length.rb +2 -2
  44. data/lib/active_model/validations/numericality.rb +48 -41
  45. data/lib/active_model/validations/presence.rb +1 -1
  46. data/lib/active_model/validations/validates.rb +6 -4
  47. data/lib/active_model/validator.rb +7 -1
  48. metadata +13 -11
@@ -11,12 +11,22 @@ module ActiveModel
11
11
  end
12
12
  end
13
13
 
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
23
+
14
24
  private
15
25
  def cast_value(value)
16
26
  case value
17
27
  when ::String then ::String.new(value)
18
- when true then "t"
19
- when false then "f"
28
+ when true then @true
29
+ when false then @false
20
30
  else value.to_s
21
31
  end
22
32
  end
@@ -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,7 +118,7 @@ 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
@@ -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)
@@ -5,7 +5,7 @@ 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
 
@@ -10,7 +10,7 @@ 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
 
@@ -24,7 +24,11 @@ module ActiveModel
24
24
  delimiter
25
25
  end
26
26
 
27
- 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
28
32
  end
29
33
 
30
34
  def delimiter
@@ -9,10 +9,10 @@ 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
@@ -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,38 @@ 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
66
+ parse_float(raw_value, precision, scale)
93
67
  elsif raw_value.is_a?(Numeric)
94
68
  raw_value
95
69
  elsif is_integer?(raw_value)
96
70
  raw_value.to_i
97
71
  elsif !is_hexadecimal_literal?(raw_value)
98
- Kernel.Float(raw_value).to_d
72
+ parse_float(Kernel.Float(raw_value), precision, scale)
99
73
  end
100
74
  end
101
75
 
76
+ def parse_float(raw_value, precision, scale)
77
+ (scale ? raw_value.truncate(scale) : raw_value).to_d(precision)
78
+ end
79
+
80
+ def is_number?(raw_value, precision, scale)
81
+ !parse_as_number(raw_value, precision, scale).nil?
82
+ rescue ArgumentError, TypeError
83
+ false
84
+ end
85
+
102
86
  def is_integer?(raw_value)
103
87
  INTEGER_REGEX.match?(raw_value.to_s)
104
88
  end
@@ -124,6 +108,27 @@ module ActiveModel
124
108
  end
125
109
  end
126
110
 
111
+ def prepare_value_for_validation(value, record, attr_name)
112
+ return value if record_attribute_changed_in_place?(record, attr_name)
113
+
114
+ came_from_user = :"#{attr_name}_came_from_user?"
115
+
116
+ if record.respond_to?(came_from_user)
117
+ if record.public_send(came_from_user)
118
+ raw_value = record.public_send(:"#{attr_name}_before_type_cast")
119
+ elsif record.respond_to?(:read_attribute)
120
+ raw_value = record.read_attribute(attr_name)
121
+ end
122
+ else
123
+ before_type_cast = :"#{attr_name}_before_type_cast"
124
+ if record.respond_to?(before_type_cast)
125
+ raw_value = record.public_send(before_type_cast)
126
+ end
127
+ end
128
+
129
+ raw_value || value
130
+ end
131
+
127
132
  def record_attribute_changed_in_place?(record, attr_name)
128
133
  record.respond_to?(:attribute_changed_in_place?) &&
129
134
  record.attribute_changed_in_place?(attr_name.to_s)
@@ -134,7 +139,8 @@ module ActiveModel
134
139
  # Validates whether the value of the specified attribute is numeric by
135
140
  # trying to convert it to a float with Kernel.Float (if <tt>only_integer</tt>
136
141
  # is +false+) or applying it to the regular expression <tt>/\A[\+\-]?\d+\z/</tt>
137
- # (if <tt>only_integer</tt> is set to +true+).
142
+ # (if <tt>only_integer</tt> is set to +true+). Precision of Kernel.Float values
143
+ # are guaranteed up to 15 digits.
138
144
  #
139
145
  # class Person < ActiveRecord::Base
140
146
  # validates_numericality_of :value, on: :create
@@ -175,6 +181,7 @@ module ActiveModel
175
181
  # * <tt>:less_than</tt>
176
182
  # * <tt>:less_than_or_equal_to</tt>
177
183
  # * <tt>:only_integer</tt>
184
+ # * <tt>:other_than</tt>
178
185
  #
179
186
  # For example:
180
187
  #
@@ -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
@@ -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
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.3.2
4
+ version: 6.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-17 00:00:00.000000000 Z
11
+ date: 2020-12-09 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.3.2
19
+ version: 6.1.0
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.3.2
26
+ version: 6.1.0
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,11 +104,11 @@ 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.3.2/activemodel/CHANGELOG.md
106
- documentation_uri: https://api.rubyonrails.org/v6.0.3.2/
107
+ changelog_uri: https://github.com/rails/rails/blob/v6.1.0/activemodel/CHANGELOG.md
108
+ documentation_uri: https://api.rubyonrails.org/v6.1.0/
107
109
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
108
- source_code_uri: https://github.com/rails/rails/tree/v6.0.3.2/activemodel
109
- post_install_message:
110
+ source_code_uri: https://github.com/rails/rails/tree/v6.1.0/activemodel
111
+ post_install_message:
110
112
  rdoc_options: []
111
113
  require_paths:
112
114
  - lib
@@ -121,8 +123,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
123
  - !ruby/object:Gem::Version
122
124
  version: '0'
123
125
  requirements: []
124
- rubygems_version: 3.1.2
125
- signing_key:
126
+ rubygems_version: 3.1.4
127
+ signing_key:
126
128
  specification_version: 4
127
129
  summary: A toolkit for building modeling frameworks (part of Rails).
128
130
  test_files: []