activemodel 7.0.4 → 6.1.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +101 -103
  3. data/MIT-LICENSE +0 -1
  4. data/README.rdoc +3 -3
  5. data/lib/active_model/attribute.rb +0 -4
  6. data/lib/active_model/attribute_methods.rb +82 -67
  7. data/lib/active_model/attribute_set/builder.rb +10 -1
  8. data/lib/active_model/attribute_set.rb +1 -4
  9. data/lib/active_model/attributes.rb +12 -15
  10. data/lib/active_model/callbacks.rb +3 -3
  11. data/lib/active_model/conversion.rb +2 -2
  12. data/lib/active_model/dirty.rb +4 -5
  13. data/lib/active_model/error.rb +2 -2
  14. data/lib/active_model/errors.rb +248 -55
  15. data/lib/active_model/gem_version.rb +5 -5
  16. data/lib/active_model/locale/en.yml +0 -1
  17. data/lib/active_model/model.rb +59 -6
  18. data/lib/active_model/naming.rb +8 -15
  19. data/lib/active_model/secure_password.rb +2 -25
  20. data/lib/active_model/serialization.rb +2 -6
  21. data/lib/active_model/translation.rb +2 -2
  22. data/lib/active_model/type/date.rb +1 -1
  23. data/lib/active_model/type/helpers/numeric.rb +1 -9
  24. data/lib/active_model/type/helpers/time_value.rb +3 -3
  25. data/lib/active_model/type/integer.rb +1 -4
  26. data/lib/active_model/type/registry.rb +38 -8
  27. data/lib/active_model/type/time.rb +1 -1
  28. data/lib/active_model/type.rb +6 -6
  29. data/lib/active_model/validations/absence.rb +2 -2
  30. data/lib/active_model/validations/acceptance.rb +1 -1
  31. data/lib/active_model/validations/callbacks.rb +1 -1
  32. data/lib/active_model/validations/clusivity.rb +1 -1
  33. data/lib/active_model/validations/confirmation.rb +5 -5
  34. data/lib/active_model/validations/exclusion.rb +3 -3
  35. data/lib/active_model/validations/format.rb +1 -1
  36. data/lib/active_model/validations/inclusion.rb +3 -3
  37. data/lib/active_model/validations/length.rb +2 -2
  38. data/lib/active_model/validations/numericality.rb +22 -29
  39. data/lib/active_model/validations/presence.rb +1 -1
  40. data/lib/active_model/validations/validates.rb +3 -3
  41. data/lib/active_model/validations/with.rb +4 -4
  42. data/lib/active_model/validations.rb +12 -12
  43. data/lib/active_model/validator.rb +5 -5
  44. data/lib/active_model/version.rb +1 -1
  45. data/lib/active_model.rb +0 -1
  46. metadata +9 -12
  47. data/lib/active_model/api.rb +0 -99
  48. data/lib/active_model/validations/comparability.rb +0 -29
  49. data/lib/active_model/validations/comparison.rb +0 -82
@@ -3,7 +3,6 @@
3
3
  require "active_support/core_ext/hash/except"
4
4
  require "active_support/core_ext/module/introspection"
5
5
  require "active_support/core_ext/module/redefine_method"
6
- require "active_support/core_ext/module/delegation"
7
6
 
8
7
  module ActiveModel
9
8
  class Name
@@ -154,7 +153,6 @@ module ActiveModel
154
153
  # Returns a new ActiveModel::Name instance. By default, the +namespace+
155
154
  # and +name+ option will take the namespace and name of the given class
156
155
  # respectively.
157
- # Use +locale+ argument for singularize and pluralize model name.
158
156
  #
159
157
  # module Foo
160
158
  # class Bar
@@ -163,7 +161,7 @@ module ActiveModel
163
161
  #
164
162
  # ActiveModel::Name.new(Foo::Bar).to_s
165
163
  # # => "Foo::Bar"
166
- def initialize(klass, namespace = nil, name = nil, locale = :en)
164
+ def initialize(klass, namespace = nil, name = nil)
167
165
  @name = name || klass.name
168
166
 
169
167
  raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if @name.blank?
@@ -171,17 +169,16 @@ module ActiveModel
171
169
  @unnamespaced = @name.delete_prefix("#{namespace.name}::") if namespace
172
170
  @klass = klass
173
171
  @singular = _singularize(@name)
174
- @plural = ActiveSupport::Inflector.pluralize(@singular, locale)
175
- @uncountable = @plural == @singular
172
+ @plural = ActiveSupport::Inflector.pluralize(@singular)
176
173
  @element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(@name))
177
174
  @human = ActiveSupport::Inflector.humanize(@element)
178
175
  @collection = ActiveSupport::Inflector.tableize(@name)
179
176
  @param_key = (namespace ? _singularize(@unnamespaced) : @singular)
180
177
  @i18n_key = @name.underscore.to_sym
181
178
 
182
- @route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key, locale) : @plural.dup)
183
- @singular_route_key = ActiveSupport::Inflector.singularize(@route_key, locale)
184
- @route_key << "_index" if @uncountable
179
+ @route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural.dup)
180
+ @singular_route_key = ActiveSupport::Inflector.singularize(@route_key)
181
+ @route_key << "_index" if @plural == @singular
185
182
  end
186
183
 
187
184
  # Transform the model name into a more human format, using I18n. By default,
@@ -209,10 +206,6 @@ module ActiveModel
209
206
  I18n.translate(defaults.shift, **options)
210
207
  end
211
208
 
212
- def uncountable?
213
- @uncountable
214
- end
215
-
216
209
  private
217
210
  def _singularize(string)
218
211
  ActiveSupport::Inflector.underscore(string).tr("/", "_")
@@ -239,7 +232,7 @@ module ActiveModel
239
232
  # is required to pass the \Active \Model Lint test. So either extending the
240
233
  # provided method below, or rolling your own is required.
241
234
  module Naming
242
- def self.extended(base) # :nodoc:
235
+ def self.extended(base) #:nodoc:
243
236
  base.silence_redefinition_of_method :model_name
244
237
  base.delegate :model_name, to: :class
245
238
  end
@@ -286,7 +279,7 @@ module ActiveModel
286
279
  # ActiveModel::Naming.uncountable?(Sheep) # => true
287
280
  # ActiveModel::Naming.uncountable?(Post) # => false
288
281
  def self.uncountable?(record_or_class)
289
- model_name_from_record_or_class(record_or_class).uncountable?
282
+ plural(record_or_class) == singular(record_or_class)
290
283
  end
291
284
 
292
285
  # Returns string to use while generating route names. It differs for
@@ -328,7 +321,7 @@ module ActiveModel
328
321
  model_name_from_record_or_class(record_or_class).param_key
329
322
  end
330
323
 
331
- def self.model_name_from_record_or_class(record_or_class) # :nodoc:
324
+ def self.model_name_from_record_or_class(record_or_class) #:nodoc:
332
325
  if record_or_class.respond_to?(:to_model)
333
326
  record_or_class.to_model.model_name
334
327
  else
@@ -36,9 +36,7 @@ module ActiveModel
36
36
  #
37
37
  # gem 'bcrypt', '~> 3.1.7'
38
38
  #
39
- # ==== Examples
40
- #
41
- # ===== Using Active Record (which automatically includes ActiveModel::SecurePassword)
39
+ # Example using Active Record (which automatically includes ActiveModel::SecurePassword):
42
40
  #
43
41
  # # Schema: User(name:string, password_digest:string, recovery_password_digest:string)
44
42
  # class User < ActiveRecord::Base
@@ -60,27 +58,6 @@ module ActiveModel
60
58
  # user.authenticate_recovery_password('42password') # => user
61
59
  # User.find_by(name: 'david')&.authenticate('notright') # => false
62
60
  # User.find_by(name: 'david')&.authenticate('mUc3m00RsqyRe') # => user
63
- #
64
- # ===== Conditionally requiring a password
65
- #
66
- # class Account
67
- # include ActiveModel::SecurePassword
68
- #
69
- # attr_accessor :is_guest, :password_digest
70
- #
71
- # has_secure_password
72
- #
73
- # def errors
74
- # super.tap { |errors| errors.delete(:password, :blank) if is_guest }
75
- # end
76
- # end
77
- #
78
- # account = Account.new
79
- # account.valid? # => false, password required
80
- #
81
- # account.is_guest = true
82
- # account.valid? # => true
83
- #
84
61
  def has_secure_password(attribute = :password, validations: true)
85
62
  # Load bcrypt gem only when has_secure_password is used.
86
63
  # This is to avoid ActiveModel (and by extension the entire framework)
@@ -142,7 +119,7 @@ module ActiveModel
142
119
  # user.authenticate_password('mUc3m00RsqyRe') # => user
143
120
  define_method("authenticate_#{attribute}") do |unencrypted_password|
144
121
  attribute_digest = public_send("#{attribute}_digest")
145
- attribute_digest.present? && BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self
122
+ BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self
146
123
  end
147
124
 
148
125
  alias_method :authenticate, :authenticate_password if attribute == :password
@@ -123,7 +123,7 @@ module ActiveModel
123
123
  # user.serializable_hash(include: { notes: { only: 'title' }})
124
124
  # # => {"name" => "Napoleon", "notes" => [{"title"=>"Battle of Austerlitz"}]}
125
125
  def serializable_hash(options = nil)
126
- attribute_names = attribute_names_for_serialization
126
+ attribute_names = attributes.keys
127
127
 
128
128
  return serializable_attributes(attribute_names) if options.blank?
129
129
 
@@ -149,10 +149,6 @@ module ActiveModel
149
149
  end
150
150
 
151
151
  private
152
- def attribute_names_for_serialization
153
- attributes.keys
154
- end
155
-
156
152
  # Hook method defining how an attribute value should be retrieved for
157
153
  # serialization. By default this is assumed to be an instance named after
158
154
  # the attribute. Override this method in subclasses should you need to
@@ -181,7 +177,7 @@ module ActiveModel
181
177
  # +association+ - name of the association
182
178
  # +records+ - the association record(s) to be serialized
183
179
  # +opts+ - options for the association records
184
- def serializable_add_includes(options = {}) # :nodoc:
180
+ def serializable_add_includes(options = {}) #:nodoc:
185
181
  return unless includes = options[:include]
186
182
 
187
183
  unless includes.is_a?(Hash)
@@ -17,12 +17,12 @@ module ActiveModel
17
17
  #
18
18
  # This also provides the required class methods for hooking into the
19
19
  # Rails internationalization API, including being able to define a
20
- # class-based +i18n_scope+ and +lookup_ancestors+ to find translations in
20
+ # class based +i18n_scope+ and +lookup_ancestors+ to find translations in
21
21
  # parent classes.
22
22
  module Translation
23
23
  include ActiveModel::Naming
24
24
 
25
- # Returns the +i18n_scope+ for the class. Override if you want custom lookup.
25
+ # Returns the +i18n_scope+ for the class. Overwrite if you want custom lookup.
26
26
  def i18n_scope
27
27
  :activemodel
28
28
  end
@@ -11,7 +11,7 @@ module ActiveModel
11
11
  end
12
12
 
13
13
  def type_cast_for_schema(value)
14
- value.to_fs(:db).inspect
14
+ value.to_s(:db).inspect
15
15
  end
16
16
 
17
17
  private
@@ -25,18 +25,10 @@ module ActiveModel
25
25
  end
26
26
 
27
27
  def changed?(old_value, _new_value, new_value_before_type_cast) # :nodoc:
28
- (super || number_to_non_number?(old_value, new_value_before_type_cast)) &&
29
- !equal_nan?(old_value, new_value_before_type_cast)
28
+ super || number_to_non_number?(old_value, new_value_before_type_cast)
30
29
  end
31
30
 
32
31
  private
33
- def equal_nan?(old_value, new_value)
34
- (old_value.is_a?(::Float) || old_value.is_a?(BigDecimal)) &&
35
- old_value.nan? &&
36
- old_value.instance_of?(new_value.class) &&
37
- new_value.nan?
38
- end
39
-
40
32
  def number_to_non_number?(old_value, new_value_before_type_cast)
41
33
  old_value != nil && non_numeric_string?(new_value_before_type_cast.to_s)
42
34
  end
@@ -12,9 +12,9 @@ module ActiveModel
12
12
 
13
13
  if value.acts_like?(:time)
14
14
  if is_utc?
15
- value = value.getutc if !value.utc?
15
+ value = value.getutc if value.respond_to?(:getutc) && !value.utc?
16
16
  else
17
- value = value.getlocal
17
+ value = value.getlocal if value.respond_to?(:getlocal)
18
18
  end
19
19
  end
20
20
 
@@ -36,7 +36,7 @@ module ActiveModel
36
36
  end
37
37
 
38
38
  def type_cast_for_schema(value)
39
- value.to_fs(:db).inspect
39
+ value.to_s(:db).inspect
40
40
  end
41
41
 
42
42
  def user_input_in_time_zone(value)
@@ -30,10 +30,7 @@ module ActiveModel
30
30
 
31
31
  def serializable?(value)
32
32
  cast_value = cast(value)
33
- in_range?(cast_value) || begin
34
- yield cast_value if block_given?
35
- false
36
- end
33
+ in_range?(cast_value) && super
37
34
  end
38
35
 
39
36
  private
@@ -1,38 +1,68 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveModel
4
+ # :stopdoc:
4
5
  module Type
5
- class Registry # :nodoc:
6
+ class Registry
6
7
  def initialize
7
- @registrations = {}
8
+ @registrations = []
8
9
  end
9
10
 
10
- def initialize_copy(other)
11
+ def initialize_dup(other)
11
12
  @registrations = @registrations.dup
12
13
  super
13
14
  end
14
15
 
15
- def register(type_name, klass = nil, &block)
16
+ def register(type_name, klass = nil, **options, &block)
16
17
  unless block_given?
17
18
  block = proc { |_, *args| klass.new(*args) }
18
19
  block.ruby2_keywords if block.respond_to?(:ruby2_keywords)
19
20
  end
20
- registrations[type_name] = block
21
+ registrations << registration_klass.new(type_name, block, **options)
21
22
  end
22
23
 
23
24
  def lookup(symbol, *args)
24
- registration = registrations[symbol]
25
+ registration = find_registration(symbol, *args)
25
26
 
26
27
  if registration
27
- registration.call(symbol, *args)
28
+ registration.call(self, symbol, *args)
28
29
  else
29
30
  raise ArgumentError, "Unknown type #{symbol.inspect}"
30
31
  end
31
32
  end
32
- ruby2_keywords(:lookup)
33
+ ruby2_keywords(:lookup) if respond_to?(:ruby2_keywords, true)
33
34
 
34
35
  private
35
36
  attr_reader :registrations
37
+
38
+ def registration_klass
39
+ Registration
40
+ end
41
+
42
+ def find_registration(symbol, *args, **kwargs)
43
+ registrations.find { |r| r.matches?(symbol, *args, **kwargs) }
44
+ end
45
+ end
46
+
47
+ class Registration
48
+ # Options must be taken because of https://bugs.ruby-lang.org/issues/10856
49
+ def initialize(name, block, **)
50
+ @name = name
51
+ @block = block
52
+ end
53
+
54
+ def call(_registry, *args)
55
+ block.call(*args)
56
+ end
57
+ ruby2_keywords(:call) if respond_to?(:ruby2_keywords, true)
58
+
59
+ def matches?(type_name, *args, **kwargs)
60
+ type_name == name
61
+ end
62
+
63
+ private
64
+ attr_reader :name, :block
36
65
  end
37
66
  end
67
+ # :startdoc:
38
68
  end
@@ -33,7 +33,7 @@ module ActiveModel
33
33
  return apply_seconds_precision(value) unless value.is_a?(::String)
34
34
  return if value.empty?
35
35
 
36
- dummy_time_value = value.sub(/\A\d{4}-\d\d-\d\d(?:T|\s)|/, "2000-01-01 ")
36
+ dummy_time_value = value.sub(/\A(\d\d\d\d-\d\d-\d\d |)/, "2000-01-01 ")
37
37
 
38
38
  fast_string_to_time(dummy_time_value) || begin
39
39
  time_hash = ::Date._parse(dummy_time_value)
@@ -24,15 +24,15 @@ module ActiveModel
24
24
  class << self
25
25
  attr_accessor :registry # :nodoc:
26
26
 
27
- # Add a new type to the registry, allowing it to be referenced as a
28
- # symbol by {attribute}[rdoc-ref:Attributes::ClassMethods#attribute].
29
- def register(type_name, klass = nil, &block)
30
- registry.register(type_name, klass, &block)
27
+ # Add a new type to the registry, allowing it to be gotten through ActiveModel::Type#lookup
28
+ def register(type_name, klass = nil, **options, &block)
29
+ registry.register(type_name, klass, **options, &block)
31
30
  end
32
31
 
33
- def lookup(...) # :nodoc:
34
- registry.lookup(...)
32
+ def lookup(*args) # :nodoc:
33
+ registry.lookup(*args)
35
34
  end
35
+ ruby2_keywords(:lookup) if respond_to?(:ruby2_keywords, true)
36
36
 
37
37
  def default_value # :nodoc:
38
38
  @default_value ||= Value.new
@@ -3,7 +3,7 @@
3
3
  module ActiveModel
4
4
  module Validations
5
5
  # == \Active \Model Absence Validator
6
- class AbsenceValidator < EachValidator # :nodoc:
6
+ class AbsenceValidator < EachValidator #:nodoc:
7
7
  def validate_each(record, attr_name, value)
8
8
  record.errors.add(attr_name, :present, **options) if value.present?
9
9
  end
@@ -24,7 +24,7 @@ module ActiveModel
24
24
  #
25
25
  # There is also a list of default options supported by every validator:
26
26
  # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
27
- # See ActiveModel::Validations::ClassMethods#validates for more information.
27
+ # See <tt>ActiveModel::Validations#validates</tt> for more information
28
28
  def validates_absence_of(*attr_names)
29
29
  validates_with AbsenceValidator, _merge_attributes(attr_names)
30
30
  end
@@ -104,7 +104,7 @@ module ActiveModel
104
104
  #
105
105
  # There is also a list of default options supported by every validator:
106
106
  # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
107
- # See ActiveModel::Validations::ClassMethods#validates for more information.
107
+ # See <tt>ActiveModel::Validations#validates</tt> for more information.
108
108
  def validates_acceptance_of(*attr_names)
109
109
  validates_with AcceptanceValidator, _merge_attributes(attr_names)
110
110
  end
@@ -112,7 +112,7 @@ module ActiveModel
112
112
  end
113
113
 
114
114
  private
115
- # Override run_validations! to include callbacks.
115
+ # Overwrite run validations to include callbacks.
116
116
  def run_validations!
117
117
  _run_validation_callbacks { super }
118
118
  end
@@ -4,7 +4,7 @@ require "active_support/core_ext/range"
4
4
 
5
5
  module ActiveModel
6
6
  module Validations
7
- module Clusivity # :nodoc:
7
+ module Clusivity #:nodoc:
8
8
  ERROR_MESSAGE = "An object with the method #include? or a proc, lambda or symbol is required, " \
9
9
  "and must be supplied as the :in (or :within) option of the configuration hash"
10
10
 
@@ -19,13 +19,13 @@ module ActiveModel
19
19
 
20
20
  private
21
21
  def setup!(klass)
22
- klass.attr_reader(*attributes.filter_map do |attribute|
22
+ klass.attr_reader(*attributes.map do |attribute|
23
23
  :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation")
24
- end)
24
+ end.compact)
25
25
 
26
- klass.attr_writer(*attributes.filter_map do |attribute|
26
+ klass.attr_writer(*attributes.map do |attribute|
27
27
  :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=")
28
- end)
28
+ end.compact)
29
29
  end
30
30
 
31
31
  def confirmation_value_equal?(record, attribute, value, confirmed)
@@ -71,7 +71,7 @@ module ActiveModel
71
71
  #
72
72
  # There is also a list of default options supported by every validator:
73
73
  # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
74
- # See ActiveModel::Validations::ClassMethods#validates for more information.
74
+ # See <tt>ActiveModel::Validations#validates</tt> for more information
75
75
  def validates_confirmation_of(*attr_names)
76
76
  validates_with ConfirmationValidator, _merge_attributes(attr_names)
77
77
  end
@@ -29,8 +29,8 @@ module ActiveModel
29
29
  #
30
30
  # Configuration options:
31
31
  # * <tt>:in</tt> - An enumerable object of items that the value shouldn't
32
- # be part of. This can be supplied as a proc, lambda, or symbol which returns an
33
- # enumerable. If the enumerable is a numerical, time, or datetime range the test
32
+ # be part of. This can be supplied as a proc, lambda or symbol which returns an
33
+ # enumerable. If the enumerable is a numerical, time or datetime range the test
34
34
  # is performed with <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>. When
35
35
  # using a proc or lambda the instance under validation is passed as an argument.
36
36
  # * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
@@ -40,7 +40,7 @@ module ActiveModel
40
40
  #
41
41
  # There is also a list of default options supported by every validator:
42
42
  # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
43
- # See ActiveModel::Validations::ClassMethods#validates for more information.
43
+ # See <tt>ActiveModel::Validations#validates</tt> for more information
44
44
  def validates_exclusion_of(*attr_names)
45
45
  validates_with ExclusionValidator, _merge_attributes(attr_names)
46
46
  end
@@ -104,7 +104,7 @@ module ActiveModel
104
104
  #
105
105
  # There is also a list of default options supported by every validator:
106
106
  # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
107
- # See ActiveModel::Validations::ClassMethods#validates for more information.
107
+ # See <tt>ActiveModel::Validations#validates</tt> for more information
108
108
  def validates_format_of(*attr_names)
109
109
  validates_with FormatValidator, _merge_attributes(attr_names)
110
110
  end
@@ -28,8 +28,8 @@ module ActiveModel
28
28
  #
29
29
  # Configuration options:
30
30
  # * <tt>:in</tt> - An enumerable object of available items. This can be
31
- # supplied as a proc, lambda, or symbol which returns an enumerable. If the
32
- # enumerable is a numerical, time, or datetime range the test is performed
31
+ # supplied as a proc, lambda or symbol which returns an enumerable. If the
32
+ # enumerable is a numerical, time or datetime range the test is performed
33
33
  # with <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>. When using
34
34
  # a proc or lambda the instance under validation is passed as an argument.
35
35
  # * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
@@ -38,7 +38,7 @@ module ActiveModel
38
38
  #
39
39
  # There is also a list of default options supported by every validator:
40
40
  # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
41
- # See ActiveModel::Validations::ClassMethods#validates for more information.
41
+ # See <tt>ActiveModel::Validations#validates</tt> for more information
42
42
  def validates_inclusion_of(*attr_names)
43
43
  validates_with InclusionValidator, _merge_attributes(attr_names)
44
44
  end
@@ -117,8 +117,8 @@ module ActiveModel
117
117
  # <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
118
118
  #
119
119
  # There is also a list of default options supported by every validator:
120
- # +:if+, +:unless+, +:on+, and +:strict+.
121
- # See ActiveModel::Validations::ClassMethods#validates for more information.
120
+ # +:if+, +:unless+, +:on+ and +:strict+.
121
+ # See <tt>ActiveModel::Validations#validates</tt> for more information
122
122
  def validates_length_of(*attr_names)
123
123
  validates_with LengthValidator, _merge_attributes(attr_names)
124
124
  end
@@ -1,34 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_model/validations/comparability"
4
3
  require "bigdecimal/util"
5
4
 
6
5
  module ActiveModel
7
6
  module Validations
8
7
  class NumericalityValidator < EachValidator # :nodoc:
9
- include Comparability
8
+ CHECKS = { greater_than: :>, greater_than_or_equal_to: :>=,
9
+ equal_to: :==, less_than: :<, less_than_or_equal_to: :<=,
10
+ odd: :odd?, even: :even?, other_than: :!= }.freeze
10
11
 
11
- RANGE_CHECKS = { in: :in? }
12
- NUMBER_CHECKS = { odd: :odd?, even: :even? }
13
-
14
- RESERVED_OPTIONS = COMPARE_CHECKS.keys + NUMBER_CHECKS.keys + RANGE_CHECKS.keys + [:only_integer]
12
+ RESERVED_OPTIONS = CHECKS.keys + [:only_integer]
15
13
 
16
14
  INTEGER_REGEX = /\A[+-]?\d+\z/
17
15
 
18
16
  HEXADECIMAL_REGEX = /\A[+-]?0[xX]/
19
17
 
20
18
  def check_validity!
21
- options.slice(*COMPARE_CHECKS.keys).each do |option, value|
19
+ keys = CHECKS.keys - [:odd, :even]
20
+ options.slice(*keys).each do |option, value|
22
21
  unless value.is_a?(Numeric) || value.is_a?(Proc) || value.is_a?(Symbol)
23
22
  raise ArgumentError, ":#{option} must be a number, a symbol or a proc"
24
23
  end
25
24
  end
26
-
27
- options.slice(*RANGE_CHECKS.keys).each do |option, value|
28
- unless value.is_a?(Range)
29
- raise ArgumentError, ":#{option} must be a range"
30
- end
31
- end
32
25
  end
33
26
 
34
27
  def validate_each(record, attr_name, value, precision: Float::DIG, scale: nil)
@@ -44,18 +37,23 @@ module ActiveModel
44
37
 
45
38
  value = parse_as_number(value, precision, scale)
46
39
 
47
- options.slice(*RESERVED_OPTIONS).each do |option, option_value|
48
- if NUMBER_CHECKS.include?(option)
49
- unless value.to_i.public_send(NUMBER_CHECKS[option])
40
+ options.slice(*CHECKS.keys).each do |option, option_value|
41
+ case option
42
+ when :odd, :even
43
+ unless value.to_i.public_send(CHECKS[option])
50
44
  record.errors.add(attr_name, option, **filtered_options(value))
51
45
  end
52
- elsif RANGE_CHECKS.include?(option)
53
- unless value.public_send(RANGE_CHECKS[option], option_value)
54
- record.errors.add(attr_name, option, **filtered_options(value).merge!(count: option_value))
46
+ else
47
+ case option_value
48
+ when Proc
49
+ option_value = option_value.call(record)
50
+ when Symbol
51
+ option_value = record.send(option_value)
55
52
  end
56
- elsif COMPARE_CHECKS.include?(option)
57
- option_value = option_as_number(record, option_value, precision, scale)
58
- unless value.public_send(COMPARE_CHECKS[option], option_value)
53
+
54
+ option_value = parse_as_number(option_value, precision, scale)
55
+
56
+ unless value.public_send(CHECKS[option], option_value)
59
57
  record.errors.add(attr_name, option, **filtered_options(value).merge!(count: option_value))
60
58
  end
61
59
  end
@@ -63,10 +61,6 @@ module ActiveModel
63
61
  end
64
62
 
65
63
  private
66
- def option_as_number(record, option_value, precision, scale)
67
- parse_as_number(option_value(record, option_value), precision, scale)
68
- end
69
-
70
64
  def parse_as_number(raw_value, precision, scale)
71
65
  if raw_value.is_a?(Float)
72
66
  parse_float(raw_value, precision, scale)
@@ -161,7 +155,7 @@ module ActiveModel
161
155
  # Configuration options:
162
156
  # * <tt>:message</tt> - A custom error message (default is: "is not a number").
163
157
  # * <tt>:only_integer</tt> - Specifies whether the value has to be an
164
- # integer (default is +false+).
158
+ # integer, e.g. an integral value (default is +false+).
165
159
  # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is
166
160
  # +false+). Notice that for Integer and Float columns empty strings are
167
161
  # converted to +nil+.
@@ -179,11 +173,10 @@ module ActiveModel
179
173
  # supplied value.
180
174
  # * <tt>:odd</tt> - Specifies the value must be an odd number.
181
175
  # * <tt>:even</tt> - Specifies the value must be an even number.
182
- # * <tt>:in</tt> - Check that the value is within a range.
183
176
  #
184
177
  # There is also a list of default options supported by every validator:
185
178
  # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ .
186
- # See ActiveModel::Validations::ClassMethods#validates for more information.
179
+ # See <tt>ActiveModel::Validations#validates</tt> for more information
187
180
  #
188
181
  # The following checks can also be supplied with a proc or a symbol which
189
182
  # corresponds to a method:
@@ -30,7 +30,7 @@ module ActiveModel
30
30
  #
31
31
  # There is also a list of default options supported by every validator:
32
32
  # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
33
- # See ActiveModel::Validations::ClassMethods#validates for more information.
33
+ # See <tt>ActiveModel::Validations#validates</tt> for more information
34
34
  def validates_presence_of(*attr_names)
35
35
  validates_with PresenceValidator, _merge_attributes(attr_names)
36
36
  end
@@ -78,14 +78,14 @@ module ActiveModel
78
78
  # or an array of symbols. (e.g. <tt>on: :create</tt> or
79
79
  # <tt>on: :custom_validation_context</tt> or
80
80
  # <tt>on: [:create, :custom_validation_context]</tt>)
81
- # * <tt>:if</tt> - Specifies a method, proc, or string to call to determine
81
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
82
82
  # if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
83
83
  # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
84
84
  # proc or string should return or evaluate to a +true+ or +false+ value.
85
- # * <tt>:unless</tt> - Specifies a method, proc, or string to call to determine
85
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
86
86
  # if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
87
87
  # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
88
- # method, proc, or string should return or evaluate to a +true+ or
88
+ # method, proc or string should return or evaluate to a +true+ or
89
89
  # +false+ value.
90
90
  # * <tt>:allow_nil</tt> - Skip validation if the attribute is +nil+.
91
91
  # * <tt>:allow_blank</tt> - Skip validation if the attribute is blank.
@@ -51,16 +51,16 @@ module ActiveModel
51
51
  # or an array of symbols. (e.g. <tt>on: :create</tt> or
52
52
  # <tt>on: :custom_validation_context</tt> or
53
53
  # <tt>on: [:create, :custom_validation_context]</tt>)
54
- # * <tt>:if</tt> - Specifies a method, proc, or string to call to determine
54
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
55
55
  # if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
56
56
  # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>).
57
- # The method, proc, or string should return or evaluate to a +true+ or
57
+ # The method, proc or string should return or evaluate to a +true+ or
58
58
  # +false+ value.
59
- # * <tt>:unless</tt> - Specifies a method, proc, or string to call to
59
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to
60
60
  # determine if the validation should not occur
61
61
  # (e.g. <tt>unless: :skip_validation</tt>, or
62
62
  # <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>).
63
- # The method, proc, or string should return or evaluate to a +true+ or
63
+ # The method, proc or string should return or evaluate to a +true+ or
64
64
  # +false+ value.
65
65
  # * <tt>:strict</tt> - Specifies whether validation should be strict.
66
66
  # See <tt>ActiveModel::Validations#validates!</tt> for more information.