activemodel 7.0.8.7 → 7.1.0.beta1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +132 -226
- data/MIT-LICENSE +1 -1
- data/README.rdoc +9 -9
- data/lib/active_model/access.rb +16 -0
- data/lib/active_model/api.rb +5 -5
- data/lib/active_model/attribute/user_provided_default.rb +4 -0
- data/lib/active_model/attribute.rb +26 -1
- data/lib/active_model/attribute_assignment.rb +1 -1
- data/lib/active_model/attribute_methods.rb +102 -63
- data/lib/active_model/attribute_registration.rb +77 -0
- data/lib/active_model/attribute_set.rb +9 -0
- data/lib/active_model/attributes.rb +62 -45
- data/lib/active_model/callbacks.rb +5 -5
- data/lib/active_model/conversion.rb +14 -4
- data/lib/active_model/deprecator.rb +7 -0
- data/lib/active_model/dirty.rb +134 -13
- data/lib/active_model/error.rb +4 -3
- data/lib/active_model/errors.rb +17 -12
- data/lib/active_model/forbidden_attributes_protection.rb +2 -0
- data/lib/active_model/gem_version.rb +4 -4
- data/lib/active_model/lint.rb +1 -1
- data/lib/active_model/locale/en.yml +4 -3
- data/lib/active_model/model.rb +26 -2
- data/lib/active_model/naming.rb +29 -10
- data/lib/active_model/railtie.rb +4 -0
- data/lib/active_model/secure_password.rb +61 -23
- data/lib/active_model/serialization.rb +3 -3
- data/lib/active_model/serializers/json.rb +1 -1
- data/lib/active_model/translation.rb +18 -16
- data/lib/active_model/type/big_integer.rb +23 -1
- data/lib/active_model/type/binary.rb +7 -1
- data/lib/active_model/type/boolean.rb +11 -9
- data/lib/active_model/type/date.rb +28 -2
- data/lib/active_model/type/date_time.rb +45 -3
- data/lib/active_model/type/decimal.rb +39 -1
- data/lib/active_model/type/float.rb +30 -1
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +5 -1
- data/lib/active_model/type/helpers/numeric.rb +4 -0
- data/lib/active_model/type/helpers/time_value.rb +28 -12
- data/lib/active_model/type/immutable_string.rb +37 -1
- data/lib/active_model/type/integer.rb +44 -1
- data/lib/active_model/type/serialize_cast_value.rb +47 -0
- data/lib/active_model/type/string.rb +9 -1
- data/lib/active_model/type/time.rb +48 -7
- data/lib/active_model/type/value.rb +17 -1
- data/lib/active_model/type.rb +1 -0
- data/lib/active_model/validations/absence.rb +1 -1
- data/lib/active_model/validations/acceptance.rb +1 -1
- data/lib/active_model/validations/callbacks.rb +4 -4
- data/lib/active_model/validations/clusivity.rb +5 -8
- data/lib/active_model/validations/comparability.rb +0 -11
- data/lib/active_model/validations/comparison.rb +15 -7
- data/lib/active_model/validations/confirmation.rb +1 -1
- data/lib/active_model/validations/format.rb +6 -7
- data/lib/active_model/validations/length.rb +10 -8
- data/lib/active_model/validations/numericality.rb +35 -23
- data/lib/active_model/validations/presence.rb +2 -2
- data/lib/active_model/validations/resolve_value.rb +26 -0
- data/lib/active_model/validations/validates.rb +4 -4
- data/lib/active_model/validations/with.rb +9 -2
- data/lib/active_model/validations.rb +45 -10
- data/lib/active_model/validator.rb +7 -5
- data/lib/active_model/version.rb +1 -1
- data/lib/active_model.rb +5 -1
- metadata +18 -13
| @@ -2,7 +2,46 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            module ActiveModel
         | 
| 4 4 | 
             
              module Type
         | 
| 5 | 
            -
                 | 
| 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
         | 
| @@ -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 | 
            -
                 | 
| 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 | 
            -
                 | 
| 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 =  | 
| 23 | 
            -
             | 
| 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. | 
| 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 =  | 
| 40 | 
            -
             | 
| 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 | 
            -
                   | 
| 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
         | 
    
        data/lib/active_model/type.rb
    CHANGED
    
    
| @@ -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?). | 
| 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 | 
| 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 | 
            -
                #  | 
| 5 | 
            +
                # = Active \Model \Validation \Callbacks
         | 
| 6 6 | 
             
                #
         | 
| 7 | 
            -
                # Provides an interface for any class to have  | 
| 8 | 
            -
                #  | 
| 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
         | 
| @@ -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 =  | 
| 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. | 
| 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,11 +1,13 @@ | |
| 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 13 | 
             
                    unless (options.keys & COMPARE_CHECKS.keys).any?
         | 
| @@ -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 =  | 
| 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+ .
         | 
| @@ -64,7 +64,7 @@ module ActiveModel | |
| 64 64 | 
             
                  #   validates_presence_of :password_confirmation, if: :password_changed?
         | 
| 65 65 | 
             
                  #
         | 
| 66 66 | 
             
                  # Configuration options:
         | 
| 67 | 
            -
                  # * <tt>:message</tt> - A custom error message (default is: "doesn | 
| 67 | 
            +
                  # * <tt>:message</tt> - A custom error message (default is: "doesn’t match
         | 
| 68 68 | 
             
                  #   <tt>%{translated_attribute_name}</tt>").
         | 
| 69 69 | 
             
                  # * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
         | 
| 70 70 | 
             
                  #   non-text columns (+true+ by default).
         | 
| @@ -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 =  | 
| 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 =  | 
| 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
         | 
| @@ -1,8 +1,12 @@ | |
| 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 LengthValidator < EachValidator # :nodoc:
         | 
| 8 | 
            +
                  include ResolveValue
         | 
| 9 | 
            +
             | 
| 6 10 | 
             
                  MESSAGES  = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze
         | 
| 7 11 | 
             
                  CHECKS    = { is: :==, minimum: :>=, maximum: :<= }.freeze
         | 
| 8 12 |  | 
| @@ -11,7 +15,8 @@ module ActiveModel | |
| 11 15 | 
             
                  def initialize(options)
         | 
| 12 16 | 
             
                    if range = (options.delete(:in) || options.delete(:within))
         | 
| 13 17 | 
             
                      raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range)
         | 
| 14 | 
            -
                      options[:minimum] | 
| 18 | 
            +
                      options[:minimum] = range.min if range.begin
         | 
| 19 | 
            +
                      options[:maximum] = (range.exclude_end? ? range.end - 1 : range.end) if range.end
         | 
| 15 20 | 
             
                    end
         | 
| 16 21 |  | 
| 17 22 | 
             
                    if options[:allow_blank] == false && options[:minimum].nil? && options[:is].nil?
         | 
| @@ -31,7 +36,9 @@ module ActiveModel | |
| 31 36 | 
             
                    keys.each do |key|
         | 
| 32 37 | 
             
                      value = options[key]
         | 
| 33 38 |  | 
| 34 | 
            -
                      unless (value.is_a?(Integer) && value >= 0) || | 
| 39 | 
            +
                      unless (value.is_a?(Integer) && value >= 0) ||
         | 
| 40 | 
            +
                              value == Float::INFINITY || value == -Float::INFINITY ||
         | 
| 41 | 
            +
                              value.is_a?(Symbol) || value.is_a?(Proc)
         | 
| 35 42 | 
             
                        raise ArgumentError, ":#{key} must be a non-negative Integer, Infinity, Symbol, or Proc"
         | 
| 36 43 | 
             
                      end
         | 
| 37 44 | 
             
                    end
         | 
| @@ -45,12 +52,7 @@ module ActiveModel | |
| 45 52 | 
             
                      next unless check_value = options[key]
         | 
| 46 53 |  | 
| 47 54 | 
             
                      if !value.nil? || skip_nil_check?(key)
         | 
| 48 | 
            -
                         | 
| 49 | 
            -
                        when Proc
         | 
| 50 | 
            -
                          check_value = check_value.call(record)
         | 
| 51 | 
            -
                        when Symbol
         | 
| 52 | 
            -
                          check_value = record.send(check_value)
         | 
| 53 | 
            -
                        end
         | 
| 55 | 
            +
                        check_value = resolve_value(record, check_value)
         | 
| 54 56 | 
             
                        next if value_length.public_send(validity_check, check_value)
         | 
| 55 57 | 
             
                      end
         | 
| 56 58 |  | 
| @@ -1,17 +1,19 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 3 | 
             
            require "active_model/validations/comparability"
         | 
| 4 | 
            +
            require "active_model/validations/resolve_value"
         | 
| 4 5 | 
             
            require "bigdecimal/util"
         | 
| 5 6 |  | 
| 6 7 | 
             
            module ActiveModel
         | 
| 7 8 | 
             
              module Validations
         | 
| 8 9 | 
             
                class NumericalityValidator < EachValidator # :nodoc:
         | 
| 9 10 | 
             
                  include Comparability
         | 
| 11 | 
            +
                  include ResolveValue
         | 
| 10 12 |  | 
| 11 13 | 
             
                  RANGE_CHECKS = { in: :in? }
         | 
| 12 14 | 
             
                  NUMBER_CHECKS = { odd: :odd?, even: :even? }
         | 
| 13 15 |  | 
| 14 | 
            -
                  RESERVED_OPTIONS = COMPARE_CHECKS.keys + NUMBER_CHECKS.keys + RANGE_CHECKS.keys + [:only_integer]
         | 
| 16 | 
            +
                  RESERVED_OPTIONS = COMPARE_CHECKS.keys + NUMBER_CHECKS.keys + RANGE_CHECKS.keys + [:only_integer, :only_numeric]
         | 
| 15 17 |  | 
| 16 18 | 
             
                  INTEGER_REGEX = /\A[+-]?\d+\z/
         | 
| 17 19 |  | 
| @@ -64,7 +66,7 @@ module ActiveModel | |
| 64 66 |  | 
| 65 67 | 
             
                private
         | 
| 66 68 | 
             
                  def option_as_number(record, option_value, precision, scale)
         | 
| 67 | 
            -
                    parse_as_number( | 
| 69 | 
            +
                    parse_as_number(resolve_value(record, option_value), precision, scale)
         | 
| 68 70 | 
             
                  end
         | 
| 69 71 |  | 
| 70 72 | 
             
                  def parse_as_number(raw_value, precision, scale)
         | 
| @@ -90,6 +92,10 @@ module ActiveModel | |
| 90 92 | 
             
                  end
         | 
| 91 93 |  | 
| 92 94 | 
             
                  def is_number?(raw_value, precision, scale)
         | 
| 95 | 
            +
                    if options[:only_numeric] && !raw_value.is_a?(Numeric)
         | 
| 96 | 
            +
                      return false
         | 
| 97 | 
            +
                    end
         | 
| 98 | 
            +
             | 
| 93 99 | 
             
                    !parse_as_number(raw_value, precision, scale).nil?
         | 
| 94 100 | 
             
                  rescue ArgumentError, TypeError
         | 
| 95 101 | 
             
                    false
         | 
| @@ -110,14 +116,7 @@ module ActiveModel | |
| 110 116 | 
             
                  end
         | 
| 111 117 |  | 
| 112 118 | 
             
                  def allow_only_integer?(record)
         | 
| 113 | 
            -
                     | 
| 114 | 
            -
                    when Symbol
         | 
| 115 | 
            -
                      record.send(options[:only_integer])
         | 
| 116 | 
            -
                    when Proc
         | 
| 117 | 
            -
                      options[:only_integer].call(record)
         | 
| 118 | 
            -
                    else
         | 
| 119 | 
            -
                      options[:only_integer]
         | 
| 120 | 
            -
                    end
         | 
| 119 | 
            +
                    resolve_value(record, options[:only_integer])
         | 
| 121 120 | 
             
                  end
         | 
| 122 121 |  | 
| 123 122 | 
             
                  def prepare_value_for_validation(value, record, attr_name)
         | 
| @@ -149,10 +148,11 @@ module ActiveModel | |
| 149 148 |  | 
| 150 149 | 
             
                module HelperMethods
         | 
| 151 150 | 
             
                  # Validates whether the value of the specified attribute is numeric by
         | 
| 152 | 
            -
                  # trying to convert it to a float with Kernel.Float (if | 
| 153 | 
            -
                  # is +false+) or applying it to the regular | 
| 154 | 
            -
                  # (if <tt>only_integer</tt> is set to | 
| 155 | 
            -
                  # are guaranteed up to 15 | 
| 151 | 
            +
                  # trying to convert it to a float with +Kernel.Float+ (if
         | 
| 152 | 
            +
                  # <tt>only_integer</tt> is +false+) or applying it to the regular
         | 
| 153 | 
            +
                  # expression <tt>/\A[\+\-]?\d+\z/</tt> (if <tt>only_integer</tt> is set to
         | 
| 154 | 
            +
                  # +true+). Precision of +Kernel.Float+ values are guaranteed up to 15
         | 
| 155 | 
            +
                  # digits.
         | 
| 156 156 | 
             
                  #
         | 
| 157 157 | 
             
                  #   class Person < ActiveRecord::Base
         | 
| 158 158 | 
             
                  #     validates_numericality_of :value, on: :create
         | 
| @@ -162,24 +162,36 @@ module ActiveModel | |
| 162 162 | 
             
                  # * <tt>:message</tt> - A custom error message (default is: "is not a number").
         | 
| 163 163 | 
             
                  # * <tt>:only_integer</tt> - Specifies whether the value has to be an
         | 
| 164 164 | 
             
                  #   integer (default is +false+).
         | 
| 165 | 
            +
                  # * <tt>:only_numeric</tt> - Specifies whether the value has to be an
         | 
| 166 | 
            +
                  #   instance of Numeric (default is +false+). The default behavior is to
         | 
| 167 | 
            +
                  #   attempt parsing the value if it is a String.
         | 
| 165 168 | 
             
                  # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is
         | 
| 166 169 | 
             
                  #   +false+). Notice that for Integer and Float columns empty strings are
         | 
| 167 170 | 
             
                  #   converted to +nil+.
         | 
| 168 171 | 
             
                  # * <tt>:greater_than</tt> - Specifies the value must be greater than the
         | 
| 169 | 
            -
                  #   supplied value.
         | 
| 172 | 
            +
                  #   supplied value. The default error message for this option is _"must be
         | 
| 173 | 
            +
                  #   greater than %{count}"_.
         | 
| 170 174 | 
             
                  # * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be
         | 
| 171 | 
            -
                  #   greater than or equal the supplied value.
         | 
| 175 | 
            +
                  #   greater than or equal the supplied value. The default error message
         | 
| 176 | 
            +
                  #   for this option is _"must be greater than or equal to %{count}"_.
         | 
| 172 177 | 
             
                  # * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied
         | 
| 173 | 
            -
                  #   value.
         | 
| 178 | 
            +
                  #   value. The default error message for this option is _"must be equal to
         | 
| 179 | 
            +
                  #   %{count}"_.
         | 
| 174 180 | 
             
                  # * <tt>:less_than</tt> - Specifies the value must be less than the
         | 
| 175 | 
            -
                  #   supplied value.
         | 
| 181 | 
            +
                  #   supplied value. The default error message for this option is _"must be
         | 
| 182 | 
            +
                  #   less than %{count}"_.
         | 
| 176 183 | 
             
                  # * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less
         | 
| 177 | 
            -
                  #   than or equal the supplied value.
         | 
| 184 | 
            +
                  #   than or equal the supplied value. The default error message for this
         | 
| 185 | 
            +
                  #   option is _"must be less than or equal to %{count}"_.
         | 
| 178 186 | 
             
                  # * <tt>:other_than</tt> - Specifies the value must be other than the
         | 
| 179 | 
            -
                  #   supplied value.
         | 
| 180 | 
            -
                  #  | 
| 181 | 
            -
                  # * <tt>: | 
| 182 | 
            -
                  #  | 
| 187 | 
            +
                  #   supplied value. The default error message for this option is _"must be
         | 
| 188 | 
            +
                  #   other than %{count}"_.
         | 
| 189 | 
            +
                  # * <tt>:odd</tt> - Specifies the value must be an odd number. The default
         | 
| 190 | 
            +
                  #   error message for this option is _"must be odd"_.
         | 
| 191 | 
            +
                  # * <tt>:even</tt> - Specifies the value must be an even number. The
         | 
| 192 | 
            +
                  #   default error message for this option is _"must be even"_.
         | 
| 193 | 
            +
                  # * <tt>:in</tt> - Check that the value is within a range. The default
         | 
| 194 | 
            +
                  #   error message for this option is _"must be in %{count}"_.
         | 
| 183 195 | 
             
                  #
         | 
| 184 196 | 
             
                  # There is also a list of default options supported by every validator:
         | 
| 185 197 | 
             
                  # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ .
         | 
| @@ -10,7 +10,7 @@ module ActiveModel | |
| 10 10 |  | 
| 11 11 | 
             
                module HelperMethods
         | 
| 12 12 | 
             
                  # Validates that the specified attributes are not blank (as defined by
         | 
| 13 | 
            -
                  # Object#blank?). | 
| 13 | 
            +
                  # Object#blank?).
         | 
| 14 14 | 
             
                  #
         | 
| 15 15 | 
             
                  #   class Person < ActiveRecord::Base
         | 
| 16 16 | 
             
                  #     validates_presence_of :first_name
         | 
| @@ -26,7 +26,7 @@ module ActiveModel | |
| 26 26 | 
             
                  # <tt>false.blank? # => true</tt>.
         | 
| 27 27 | 
             
                  #
         | 
| 28 28 | 
             
                  # Configuration options:
         | 
| 29 | 
            -
                  # * <tt>:message</tt> - A custom error message (default is: "can | 
| 29 | 
            +
                  # * <tt>:message</tt> - A custom error message (default is: "can’t be blank").
         | 
| 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+.
         | 
| @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module ActiveModel
         | 
| 4 | 
            +
              module Validations
         | 
| 5 | 
            +
                module ResolveValue # :nodoc:
         | 
| 6 | 
            +
                  def resolve_value(record, value)
         | 
| 7 | 
            +
                    case value
         | 
| 8 | 
            +
                    when Proc
         | 
| 9 | 
            +
                      if value.arity == 0
         | 
| 10 | 
            +
                        value.call
         | 
| 11 | 
            +
                      else
         | 
| 12 | 
            +
                        value.call(record)
         | 
| 13 | 
            +
                      end
         | 
| 14 | 
            +
                    when Symbol
         | 
| 15 | 
            +
                      record.send(value)
         | 
| 16 | 
            +
                    else
         | 
| 17 | 
            +
                      if value.respond_to?(:call)
         | 
| 18 | 
            +
                        value.call(record)
         | 
| 19 | 
            +
                      else
         | 
| 20 | 
            +
                        value
         | 
| 21 | 
            +
                      end
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
            end
         |