activemodel 7.0.8.1 → 7.2.2.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -233
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  5. data/lib/active_model/access.rb +16 -0
  6. data/lib/active_model/api.rb +5 -5
  7. data/lib/active_model/attribute/user_provided_default.rb +4 -0
  8. data/lib/active_model/attribute.rb +27 -2
  9. data/lib/active_model/attribute_assignment.rb +4 -2
  10. data/lib/active_model/attribute_methods.rb +145 -85
  11. data/lib/active_model/attribute_registration.rb +117 -0
  12. data/lib/active_model/attribute_set.rb +10 -1
  13. data/lib/active_model/attributes.rb +78 -48
  14. data/lib/active_model/callbacks.rb +6 -6
  15. data/lib/active_model/conversion.rb +14 -4
  16. data/lib/active_model/deprecator.rb +7 -0
  17. data/lib/active_model/dirty.rb +134 -13
  18. data/lib/active_model/error.rb +4 -3
  19. data/lib/active_model/errors.rb +37 -6
  20. data/lib/active_model/forbidden_attributes_protection.rb +2 -0
  21. data/lib/active_model/gem_version.rb +3 -3
  22. data/lib/active_model/lint.rb +1 -1
  23. data/lib/active_model/locale/en.yml +1 -0
  24. data/lib/active_model/model.rb +34 -2
  25. data/lib/active_model/naming.rb +29 -10
  26. data/lib/active_model/railtie.rb +4 -0
  27. data/lib/active_model/secure_password.rb +62 -24
  28. data/lib/active_model/serialization.rb +3 -3
  29. data/lib/active_model/serializers/json.rb +1 -1
  30. data/lib/active_model/translation.rb +18 -16
  31. data/lib/active_model/type/big_integer.rb +23 -1
  32. data/lib/active_model/type/binary.rb +7 -1
  33. data/lib/active_model/type/boolean.rb +11 -9
  34. data/lib/active_model/type/date.rb +28 -2
  35. data/lib/active_model/type/date_time.rb +45 -3
  36. data/lib/active_model/type/decimal.rb +39 -1
  37. data/lib/active_model/type/float.rb +30 -1
  38. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +5 -1
  39. data/lib/active_model/type/helpers/numeric.rb +6 -1
  40. data/lib/active_model/type/helpers/time_value.rb +50 -13
  41. data/lib/active_model/type/helpers/timezone.rb +5 -1
  42. data/lib/active_model/type/immutable_string.rb +37 -1
  43. data/lib/active_model/type/integer.rb +44 -1
  44. data/lib/active_model/type/registry.rb +2 -3
  45. data/lib/active_model/type/serialize_cast_value.rb +47 -0
  46. data/lib/active_model/type/string.rb +9 -1
  47. data/lib/active_model/type/time.rb +48 -7
  48. data/lib/active_model/type/value.rb +17 -1
  49. data/lib/active_model/type.rb +1 -0
  50. data/lib/active_model/validations/absence.rb +1 -1
  51. data/lib/active_model/validations/acceptance.rb +1 -1
  52. data/lib/active_model/validations/callbacks.rb +5 -5
  53. data/lib/active_model/validations/clusivity.rb +5 -8
  54. data/lib/active_model/validations/comparability.rb +0 -11
  55. data/lib/active_model/validations/comparison.rb +16 -8
  56. data/lib/active_model/validations/format.rb +6 -7
  57. data/lib/active_model/validations/length.rb +10 -8
  58. data/lib/active_model/validations/numericality.rb +35 -23
  59. data/lib/active_model/validations/presence.rb +1 -1
  60. data/lib/active_model/validations/resolve_value.rb +26 -0
  61. data/lib/active_model/validations/validates.rb +4 -4
  62. data/lib/active_model/validations/with.rb +9 -2
  63. data/lib/active_model/validations.rb +44 -9
  64. data/lib/active_model/validator.rb +7 -5
  65. data/lib/active_model/version.rb +1 -1
  66. data/lib/active_model.rb +5 -1
  67. metadata +17 -12
@@ -8,6 +8,10 @@ module ActiveModel
8
8
  cast(value)
9
9
  end
10
10
 
11
+ def serialize_cast_value(value)
12
+ value
13
+ end
14
+
11
15
  def cast(value)
12
16
  # Checks whether the value is numeric. Spaceship operator
13
17
  # will return nil if value is not numeric.
@@ -38,7 +42,8 @@ module ActiveModel
38
42
  end
39
43
 
40
44
  def number_to_non_number?(old_value, new_value_before_type_cast)
41
- old_value != nil && non_numeric_string?(new_value_before_type_cast.to_s)
45
+ old_value != nil && !new_value_before_type_cast.is_a?(::Numeric) &&
46
+ non_numeric_string?(new_value_before_type_cast.to_s)
42
47
  end
43
48
 
44
49
  def non_numeric_string?(value)
@@ -7,7 +7,7 @@ module ActiveModel
7
7
  module Type
8
8
  module Helpers # :nodoc: all
9
9
  module TimeValue
10
- def serialize(value)
10
+ def serialize_cast_value(value)
11
11
  value = apply_seconds_precision(value)
12
12
 
13
13
  if value.acts_like?(:time)
@@ -69,20 +69,57 @@ module ActiveModel
69
69
  \z
70
70
  /x
71
71
 
72
- def fast_string_to_time(string)
73
- return unless ISO_DATETIME =~ string
74
-
75
- usec = $7.to_i
76
- usec_len = $7&.length
77
- if usec_len&.< 6
78
- usec *= 10**(6 - usec_len)
72
+ if RUBY_VERSION >= "3.2"
73
+ if Time.new(2000, 1, 1, 0, 0, 0, "-00:00").yday != 1 # Early 3.2.x had a bug
74
+ # BUG: Wrapping the Time object with Time.at because Time.new with `in:` in Ruby 3.2.0
75
+ # used to return an invalid Time object
76
+ # see: https://bugs.ruby-lang.org/issues/19292
77
+ def fast_string_to_time(string)
78
+ return unless string.include?("-") # Time.new("1234") # => 1234-01-01 00:00:00
79
+
80
+ if is_utc?
81
+ ::Time.at(::Time.new(string, in: "UTC"))
82
+ else
83
+ ::Time.new(string)
84
+ end
85
+ rescue ArgumentError
86
+ nil
87
+ end
88
+ else
89
+ def fast_string_to_time(string)
90
+ return unless string.include?("-") # Time.new("1234") # => 1234-01-01 00:00:00
91
+
92
+ if is_utc?
93
+ ::Time.new(string, in: "UTC")
94
+ else
95
+ ::Time.new(string)
96
+ end
97
+ rescue ArgumentError
98
+ nil
99
+ end
79
100
  end
80
-
81
- if $8
82
- offset = $8 == "Z" ? 0 : $8.to_i * 3600 + $9.to_i * 60
101
+ else
102
+ def fast_string_to_time(string)
103
+ return unless ISO_DATETIME =~ string
104
+
105
+ usec = $7.to_i
106
+ usec_len = $7&.length
107
+ if usec_len&.< 6
108
+ usec *= 10**(6 - usec_len)
109
+ end
110
+
111
+ if $8
112
+ offset = \
113
+ if $8 == "Z"
114
+ 0
115
+ else
116
+ offset_h, offset_m = $8.to_i, $9.to_i
117
+ offset_h.to_i * 3600 + (offset_h.negative? ? -1 : 1) * offset_m * 60
118
+ end
119
+ end
120
+
121
+ new_time($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, usec, offset)
83
122
  end
84
-
85
- new_time($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, usec, offset)
86
123
  end
87
124
  end
88
125
  end
@@ -7,7 +7,11 @@ module ActiveModel
7
7
  module Helpers # :nodoc: all
8
8
  module Timezone
9
9
  def is_utc?
10
- ::Time.zone_default.nil? || ::Time.zone_default.match?("UTC")
10
+ if default = ::Time.zone_default
11
+ default.name == "UTC"
12
+ else
13
+ true
14
+ end
11
15
  end
12
16
 
13
17
  def default_timezone
@@ -2,7 +2,39 @@
2
2
 
3
3
  module ActiveModel
4
4
  module Type
5
- class ImmutableString < Value # :nodoc:
5
+ # = Active Model \ImmutableString \Type
6
+ #
7
+ # Attribute type to represent immutable strings. It casts incoming values to
8
+ # frozen strings.
9
+ #
10
+ # class Person
11
+ # include ActiveModel::Attributes
12
+ #
13
+ # attribute :name, :immutable_string
14
+ # end
15
+ #
16
+ # person = Person.new
17
+ # person.name = 1
18
+ #
19
+ # person.name # => "1"
20
+ # person.name.frozen? # => true
21
+ #
22
+ # Values are coerced to strings using their +to_s+ method. Boolean values
23
+ # are treated differently, however: +true+ will be cast to <tt>"t"</tt> and
24
+ # +false+ will be cast to <tt>"f"</tt>. These strings can be customized when
25
+ # declaring an attribute:
26
+ #
27
+ # class Person
28
+ # include ActiveModel::Attributes
29
+ #
30
+ # attribute :active, :immutable_string, true: "aye", false: "nay"
31
+ # end
32
+ #
33
+ # person = Person.new
34
+ # person.active = true
35
+ #
36
+ # person.active # => "aye"
37
+ class ImmutableString < Value
6
38
  def initialize(**args)
7
39
  @true = -(args.delete(:true)&.to_s || "t")
8
40
  @false = -(args.delete(:false)&.to_s || "f")
@@ -22,6 +54,10 @@ module ActiveModel
22
54
  end
23
55
  end
24
56
 
57
+ def serialize_cast_value(value) # :nodoc:
58
+ value
59
+ end
60
+
25
61
  private
26
62
  def cast_value(value)
27
63
  case value
@@ -2,7 +2,46 @@
2
2
 
3
3
  module ActiveModel
4
4
  module Type
5
- class Integer < Value # :nodoc:
5
+ # = Active Model \Integer \Type
6
+ #
7
+ # Attribute type for integer representation. This type is registered under
8
+ # the +:integer+ key.
9
+ #
10
+ # class Person
11
+ # include ActiveModel::Attributes
12
+ #
13
+ # attribute :age, :integer
14
+ # end
15
+ #
16
+ # Values are cast using their +to_i+ method, except for blank strings, which
17
+ # are cast to +nil+. If a +to_i+ method is not defined or raises an error,
18
+ # the value will be cast to +nil+.
19
+ #
20
+ # person = Person.new
21
+ #
22
+ # person.age = "18"
23
+ # person.age # => 18
24
+ #
25
+ # person.age = ""
26
+ # person.age # => nil
27
+ #
28
+ # person.age = :not_an_integer
29
+ # person.age # => nil (because Symbol does not define #to_i)
30
+ #
31
+ # Serialization also works under the same principle. Non-numeric strings are
32
+ # serialized as +nil+, for example.
33
+ #
34
+ # Serialization also validates that the integer can be stored using a
35
+ # limited number of bytes. If it cannot, an ActiveModel::RangeError will be
36
+ # raised. The default limit is 4 bytes, and can be customized when declaring
37
+ # an attribute:
38
+ #
39
+ # class Person
40
+ # include ActiveModel::Attributes
41
+ #
42
+ # attribute :age, :integer, limit: 6
43
+ # end
44
+ class Integer < Value
6
45
  include Helpers::Numeric
7
46
 
8
47
  # Column storage size in bytes.
@@ -28,6 +67,10 @@ module ActiveModel
28
67
  ensure_in_range(super)
29
68
  end
30
69
 
70
+ def serialize_cast_value(value) # :nodoc:
71
+ ensure_in_range(value)
72
+ end
73
+
31
74
  def serializable?(value)
32
75
  cast_value = cast(value)
33
76
  in_range?(cast_value) || begin
@@ -20,16 +20,15 @@ module ActiveModel
20
20
  registrations[type_name] = block
21
21
  end
22
22
 
23
- def lookup(symbol, *args)
23
+ def lookup(symbol, ...)
24
24
  registration = registrations[symbol]
25
25
 
26
26
  if registration
27
- registration.call(symbol, *args)
27
+ registration.call(symbol, ...)
28
28
  else
29
29
  raise ArgumentError, "Unknown type #{symbol.inspect}"
30
30
  end
31
31
  end
32
- ruby2_keywords(:lookup)
33
32
 
34
33
  private
35
34
  attr_reader :registrations
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ module SerializeCastValue # :nodoc:
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ def serialize_cast_value_compatible?
10
+ return @serialize_cast_value_compatible if defined?(@serialize_cast_value_compatible)
11
+ @serialize_cast_value_compatible = ancestors.index(instance_method(:serialize_cast_value).owner) <= ancestors.index(instance_method(:serialize).owner)
12
+ end
13
+ end
14
+
15
+ module DefaultImplementation
16
+ def serialize_cast_value(value)
17
+ value
18
+ end
19
+ end
20
+
21
+ def self.included(klass)
22
+ klass.include DefaultImplementation unless klass.method_defined?(:serialize_cast_value)
23
+ end
24
+
25
+ def self.serialize(type, value)
26
+ # Using `type.equal?(type.itself_if_...)` is a performant way to also
27
+ # ensure that `type` is not just a DelegateClass instance (e.g.
28
+ # ActiveRecord::Type::Serialized) unintentionally delegating
29
+ # SerializeCastValue methods.
30
+ if type.equal?((type.itself_if_serialize_cast_value_compatible rescue nil))
31
+ type.serialize_cast_value(value)
32
+ else
33
+ type.serialize(value)
34
+ end
35
+ end
36
+
37
+ def itself_if_serialize_cast_value_compatible
38
+ self if self.class.serialize_cast_value_compatible?
39
+ end
40
+
41
+ def initialize(...)
42
+ super
43
+ self.class.serialize_cast_value_compatible? # eagerly compute
44
+ end
45
+ end
46
+ end
47
+ end
@@ -4,7 +4,15 @@ require "active_model/type/immutable_string"
4
4
 
5
5
  module ActiveModel
6
6
  module Type
7
- class String < ImmutableString # :nodoc:
7
+ # = Active Model \String \Type
8
+ #
9
+ # Attribute type for strings. It is registered under the +:string+ key.
10
+ #
11
+ # This class is a specialization of ActiveModel::Type::ImmutableString. It
12
+ # performs coercion in the same way, and can be configured in the same way.
13
+ # However, it accounts for mutable strings, so dirty tracking can properly
14
+ # check if a string has changed.
15
+ class String < ImmutableString
8
16
  def changed_in_place?(raw_old_value, new_value)
9
17
  if new_value.is_a?(::String)
10
18
  raw_old_value != new_value
@@ -2,12 +2,45 @@
2
2
 
3
3
  module ActiveModel
4
4
  module Type
5
- class Time < Value # :nodoc:
5
+ # = Active Model \Time \Type
6
+ #
7
+ # Attribute type for time of day representation. It is registered under the
8
+ # +:time+ key.
9
+ #
10
+ # class Event
11
+ # include ActiveModel::Attributes
12
+ #
13
+ # attribute :start, :time
14
+ # end
15
+ #
16
+ # String values are parsed using the ISO 8601 datetime format, but are
17
+ # normalized to have a date of 2000-01-01 and be in the UTC time zone.
18
+ #
19
+ # event = Event.new
20
+ # event.start = "2004-10-25T01:23:45-06:00"
21
+ #
22
+ # event.start.class # => Time
23
+ # event.start # => 2000-01-01 07:23:45 UTC
24
+ #
25
+ # Partial time-only formats are also accepted.
26
+ #
27
+ # event.start = "00:01:02+03:00"
28
+ # event.start # => 1999-12-31 21:01:02 UTC
29
+ #
30
+ # The degree of sub-second precision can be customized when declaring an
31
+ # attribute:
32
+ #
33
+ # class Event
34
+ # include ActiveModel::Attributes
35
+ #
36
+ # attribute :start, :time, precision: 4
37
+ # end
38
+ class Time < Value
6
39
  include Helpers::Timezone
7
- include Helpers::TimeValue
8
40
  include Helpers::AcceptsMultiparameterTime.new(
9
41
  defaults: { 1 => 2000, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
10
42
  )
43
+ include Helpers::TimeValue
11
44
 
12
45
  def type
13
46
  :time
@@ -19,8 +52,12 @@ module ActiveModel
19
52
  case value
20
53
  when ::String
21
54
  value = "2000-01-01 #{value}"
22
- time_hash = ::Date._parse(value)
23
- return if time_hash[:hour].nil?
55
+ time_hash = begin
56
+ ::Date._parse(value)
57
+ rescue ArgumentError
58
+ end
59
+
60
+ return if time_hash.nil? || time_hash[:hour].nil?
24
61
  when ::Time
25
62
  value = value.change(year: 2000, day: 1, month: 1)
26
63
  end
@@ -31,13 +68,17 @@ module ActiveModel
31
68
  private
32
69
  def cast_value(value)
33
70
  return apply_seconds_precision(value) unless value.is_a?(::String)
34
- return if value.empty?
71
+ return if value.blank?
35
72
 
36
73
  dummy_time_value = value.sub(/\A\d{4}-\d\d-\d\d(?:T|\s)|/, "2000-01-01 ")
37
74
 
38
75
  fast_string_to_time(dummy_time_value) || begin
39
- time_hash = ::Date._parse(dummy_time_value)
40
- return if time_hash[:hour].nil?
76
+ time_hash = begin
77
+ ::Date._parse(dummy_time_value)
78
+ rescue ArgumentError
79
+ end
80
+
81
+ return if time_hash.nil? || time_hash[:hour].nil?
41
82
  new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
42
83
  end
43
84
  end
@@ -2,10 +2,20 @@
2
2
 
3
3
  module ActiveModel
4
4
  module Type
5
+ # = Active Model \Value \Type
6
+ #
7
+ # The base class for all attribute types. This class also serves as the
8
+ # default type for attributes that do not specify a type.
5
9
  class Value
10
+ include SerializeCastValue
6
11
  attr_reader :precision, :scale, :limit
7
12
 
13
+ # Initializes a type with three basic configuration settings: precision,
14
+ # limit, and scale. The Value base class does not define behavior for
15
+ # these settings. It uses them for equality comparison and hash key
16
+ # generation only.
8
17
  def initialize(precision: nil, limit: nil, scale: nil)
18
+ super()
9
19
  @precision = precision
10
20
  @scale = scale
11
21
  @limit = limit
@@ -19,7 +29,9 @@ module ActiveModel
19
29
  true
20
30
  end
21
31
 
22
- def type # :nodoc:
32
+ # Returns the unique type name as a Symbol. Subclasses should override
33
+ # this method.
34
+ def type
23
35
  end
24
36
 
25
37
  # Converts a value from database input to the appropriate ruby type. The
@@ -129,6 +141,10 @@ module ActiveModel
129
141
  false
130
142
  end
131
143
 
144
+ def as_json(*)
145
+ raise NoMethodError
146
+ end
147
+
132
148
  private
133
149
  # Convenience method for types which do not need separate type casting
134
150
  # behavior for user and database inputs. Called by Value#cast for
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_model/type/helpers"
4
+ require "active_model/type/serialize_cast_value"
4
5
  require "active_model/type/value"
5
6
 
6
7
  require "active_model/type/big_integer"
@@ -11,7 +11,7 @@ module ActiveModel
11
11
 
12
12
  module HelperMethods
13
13
  # Validates that the specified attributes are blank (as defined by
14
- # Object#present?). Happens by default on save.
14
+ # Object#present?).
15
15
  #
16
16
  # class Person < ActiveRecord::Base
17
17
  # validates_absence_of :first_name
@@ -90,7 +90,7 @@ module ActiveModel
90
90
  #
91
91
  # If the database column does not exist, the +terms_of_service+ attribute
92
92
  # is entirely virtual. This check is performed only if +terms_of_service+
93
- # is not +nil+ and by default on save.
93
+ # is not +nil+.
94
94
  #
95
95
  # Configuration options:
96
96
  # * <tt>:message</tt> - A custom error message (default is: "must be
@@ -2,12 +2,12 @@
2
2
 
3
3
  module ActiveModel
4
4
  module Validations
5
- # == Active \Model \Validation \Callbacks
5
+ # = Active \Model \Validation \Callbacks
6
6
  #
7
- # Provides an interface for any class to have +before_validation+ and
8
- # +after_validation+ callbacks.
7
+ # Provides an interface for any class to have ClassMethods#before_validation and
8
+ # ClassMethods#after_validation callbacks.
9
9
  #
10
- # First, include ActiveModel::Validations::Callbacks from the class you are
10
+ # First, include +ActiveModel::Validations::Callbacks+ from the class you are
11
11
  # creating:
12
12
  #
13
13
  # class MyModel
@@ -101,7 +101,7 @@ module ActiveModel
101
101
  options[:on] = Array(options[:on])
102
102
  options[:if] = [
103
103
  ->(o) {
104
- !(options[:on] & Array(o.validation_context)).empty?
104
+ options[:on].intersect?(Array(o.validation_context))
105
105
  },
106
106
  *options[:if]
107
107
  ]
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_model/validations/resolve_value"
3
4
  require "active_support/core_ext/range"
4
5
 
5
6
  module ActiveModel
6
7
  module Validations
7
8
  module Clusivity # :nodoc:
9
+ include ResolveValue
10
+
8
11
  ERROR_MESSAGE = "An object with the method #include? or a proc, lambda or symbol is required, " \
9
12
  "and must be supplied as the :in (or :within) option of the configuration hash"
10
13
 
@@ -16,13 +19,7 @@ module ActiveModel
16
19
 
17
20
  private
18
21
  def include?(record, value)
19
- members = if delimiter.respond_to?(:call)
20
- delimiter.call(record)
21
- elsif delimiter.respond_to?(:to_sym)
22
- record.send(delimiter)
23
- else
24
- delimiter
25
- end
22
+ members = resolve_value(record, delimiter)
26
23
 
27
24
  if value.is_a?(Array)
28
25
  value.all? { |v| members.public_send(inclusion_method(members), v) }
@@ -42,7 +39,7 @@ module ActiveModel
42
39
  # or DateTime ranges.
43
40
  def inclusion_method(enumerable)
44
41
  if enumerable.is_a? Range
45
- case enumerable.first
42
+ case enumerable.begin || enumerable.end
46
43
  when Numeric, Time, DateTime, Date
47
44
  :cover?
48
45
  else
@@ -7,17 +7,6 @@ module ActiveModel
7
7
  equal_to: :==, less_than: :<, less_than_or_equal_to: :<=,
8
8
  other_than: :!= }.freeze
9
9
 
10
- def option_value(record, option_value)
11
- case option_value
12
- when Proc
13
- option_value.call(record)
14
- when Symbol
15
- record.send(option_value)
16
- else
17
- option_value
18
- end
19
- end
20
-
21
10
  def error_options(value, option_value)
22
11
  options.except(*COMPARE_CHECKS.keys).merge!(
23
12
  count: option_value,
@@ -1,14 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_model/validations/comparability"
4
+ require "active_model/validations/resolve_value"
4
5
 
5
6
  module ActiveModel
6
7
  module Validations
7
8
  class ComparisonValidator < EachValidator # :nodoc:
8
9
  include Comparability
10
+ include ResolveValue
9
11
 
10
12
  def check_validity!
11
- unless (options.keys & COMPARE_CHECKS.keys).any?
13
+ unless options.keys.intersect?(COMPARE_CHECKS.keys)
12
14
  raise ArgumentError, "Expected one of :greater_than, :greater_than_or_equal_to, "\
13
15
  ":equal_to, :less_than, :less_than_or_equal_to, or :other_than option to be supplied."
14
16
  end
@@ -16,7 +18,7 @@ module ActiveModel
16
18
 
17
19
  def validate_each(record, attr_name, value)
18
20
  options.slice(*COMPARE_CHECKS.keys).each do |option, raw_option_value|
19
- option_value = option_value(record, raw_option_value)
21
+ option_value = resolve_value(record, raw_option_value)
20
22
 
21
23
  if value.nil? || value.blank?
22
24
  return record.errors.add(attr_name, :blank, **error_options(value, option_value))
@@ -42,17 +44,23 @@ module ActiveModel
42
44
  # Configuration options:
43
45
  # * <tt>:message</tt> - A custom error message (default is: "failed comparison").
44
46
  # * <tt>:greater_than</tt> - Specifies the value must be greater than the
45
- # supplied value.
47
+ # supplied value. The default error message for this option is _"must be
48
+ # greater than %{count}"_.
46
49
  # * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be
47
- # greater than or equal to the supplied value.
50
+ # greater than or equal to the supplied value. The default error message
51
+ # for this option is _"must be greater than or equal to %{count}"_.
48
52
  # * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied
49
- # value.
53
+ # value. The default error message for this option is _"must be equal to
54
+ # %{count}"_.
50
55
  # * <tt>:less_than</tt> - Specifies the value must be less than the
51
- # supplied value.
56
+ # supplied value. The default error message for this option is _"must be
57
+ # less than %{count}"_.
52
58
  # * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less
53
- # than or equal to the supplied value.
59
+ # than or equal to the supplied value. The default error message for
60
+ # this option is _"must be less than or equal to %{count}"_.
54
61
  # * <tt>:other_than</tt> - Specifies the value must not be equal to the
55
- # supplied value.
62
+ # supplied value. The default error message for this option is _"must be
63
+ # other than %{count}"_.
56
64
  #
57
65
  # There is also a list of default options supported by every validator:
58
66
  # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ .
@@ -1,14 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_model/validations/resolve_value"
4
+
3
5
  module ActiveModel
4
6
  module Validations
5
7
  class FormatValidator < EachValidator # :nodoc:
8
+ include ResolveValue
9
+
6
10
  def validate_each(record, attribute, value)
7
11
  if options[:with]
8
- regexp = option_call(record, :with)
12
+ regexp = resolve_value(record, options[:with])
9
13
  record_error(record, attribute, :with, value) unless regexp.match?(value.to_s)
10
14
  elsif options[:without]
11
- regexp = option_call(record, :without)
15
+ regexp = resolve_value(record, options[:without])
12
16
  record_error(record, attribute, :without, value) if regexp.match?(value.to_s)
13
17
  end
14
18
  end
@@ -23,11 +27,6 @@ module ActiveModel
23
27
  end
24
28
 
25
29
  private
26
- def option_call(record, name)
27
- option = options[name]
28
- option.respond_to?(:call) ? option.call(record) : option
29
- end
30
-
31
30
  def record_error(record, attribute, name, value)
32
31
  record.errors.add(attribute, :invalid, **options.except(name).merge!(value: value))
33
32
  end