can_has_validations 1.0.2 → 1.3.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.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -1
- data/README.md +81 -4
- data/lib/can_has_validations.rb +1 -1
- data/lib/can_has_validations/locale/en.yml +3 -1
- data/lib/can_has_validations/locale/es.yml +12 -0
- data/lib/can_has_validations/validators/array_validator.rb +29 -5
- data/lib/can_has_validations/validators/email_validator.rb +1 -1
- data/lib/can_has_validations/validators/grandparent_validator.rb +3 -2
- data/lib/can_has_validations/validators/hash_keys_validator.rb +30 -0
- data/lib/can_has_validations/validators/hash_values_validator.rb +55 -0
- data/lib/can_has_validations/validators/hostname_validator.rb +7 -4
- data/lib/can_has_validations/validators/ipaddr_validator.rb +91 -0
- data/lib/can_has_validations/validators/ordering_validator.rb +2 -2
- data/lib/can_has_validations/validators/url_validator.rb +1 -1
- data/lib/can_has_validations/validators/write_once_validator.rb +12 -6
- data/lib/can_has_validations/version.rb +1 -1
- metadata +16 -10
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 8ebceb28bd42a889a2d7b4d8d589dcdc25bf722d2a1f028de00dabeb92beb826
         | 
| 4 | 
            +
              data.tar.gz: 6acf11da982dddf4702db8c386af8d0780d940f9b8c4273e881e18fb0a4bc12b
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 89327c90cb07c1663fbe9cfa245eb62d964499e3b05220de5bc5ed5443f153ea5613249f9e1afec65e5b29c345a43484dd7cafd1988d76a633c97c5ac62819b3
         | 
| 7 | 
            +
              data.tar.gz: 177ce2aedb07dd299209d8099b52c3344cfa7f157682a3edc67ea27adce8853d51779138f191bb12171de5ab72976c5bd512c80eaa42b1f6575919c580b636a7
         | 
    
        data/MIT-LICENSE
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -9,7 +9,10 @@ Validations provided: | |
| 9 9 | 
             
            * Email
         | 
| 10 10 | 
             
            * Existence
         | 
| 11 11 | 
             
            * Grandparent
         | 
| 12 | 
            +
            * Hash Keys
         | 
| 13 | 
            +
            * Hash Values
         | 
| 12 14 | 
             
            * Hostname
         | 
| 15 | 
            +
            * IP address
         | 
| 13 16 | 
             
            * Ordering
         | 
| 14 17 | 
             
            * URL
         | 
| 15 18 | 
             
            * Write Once
         | 
| @@ -134,6 +137,42 @@ or the database foreign key (`:user_id`). You can also use any other field. The | |
| 134 137 | 
             
            test is merely that they match, not that they are associations.
         | 
| 135 138 |  | 
| 136 139 |  | 
| 140 | 
            +
            ## Hash Keys validator ##
         | 
| 141 | 
            +
             | 
| 142 | 
            +
            Many databases now allow storing hashes. This validates the keys of those
         | 
| 143 | 
            +
            hashes. It is conceptually the same as using the Array validator to validate
         | 
| 144 | 
            +
            `hash_attribute.keys`.
         | 
| 145 | 
            +
             | 
| 146 | 
            +
            It is able to use most existing validators that themselves work on individual
         | 
| 147 | 
            +
            attribute values (including standard Rails validators, others that are part of
         | 
| 148 | 
            +
            this gem, and likely many from other gems too).
         | 
| 149 | 
            +
             | 
| 150 | 
            +
            By default it reports only one validation error per sub-validator, regardless
         | 
| 151 | 
            +
            of how many keys fail validation. Use `multiple_errors: true` to report all
         | 
| 152 | 
            +
            errors. See Array validator for more details.
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                validates :subjects,
         | 
| 155 | 
            +
                  hash_keys: {
         | 
| 156 | 
            +
                    format: /\A[a-z]+\z/,
         | 
| 157 | 
            +
                    # multiple_errors: true
         | 
| 158 | 
            +
                  }
         | 
| 159 | 
            +
             | 
| 160 | 
            +
             | 
| 161 | 
            +
            ## Hash Values validator ##
         | 
| 162 | 
            +
             | 
| 163 | 
            +
            This is the companion to the Hash Keys validator and validates hash values
         | 
| 164 | 
            +
            instead. It is conceptually the same as using the Array validator to validate
         | 
| 165 | 
            +
            `hash_attribute.values`.
         | 
| 166 | 
            +
             | 
| 167 | 
            +
            See Hash Keys validator for more details.
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                validates :subjects,
         | 
| 170 | 
            +
                  hash_values: {
         | 
| 171 | 
            +
                    length: 3..100,
         | 
| 172 | 
            +
                    # multiple_errors: true
         | 
| 173 | 
            +
                  }
         | 
| 174 | 
            +
             | 
| 175 | 
            +
             | 
| 137 176 | 
             
            ## Hostname validator ##
         | 
| 138 177 |  | 
| 139 178 | 
             
            Ensures an attribute is generally formatted as a hostname. It allows for any
         | 
| @@ -147,6 +186,9 @@ TLD, so as to not fail as ICANN continues to add TLDs. | |
| 147 186 | 
             
                # allows '_abc.example.com'
         | 
| 148 187 | 
             
                validates :domain, hostname: {allow_underscore: true}
         | 
| 149 188 |  | 
| 189 | 
            +
                # allows '4.0/25.3.2.1.example.com'
         | 
| 190 | 
            +
                validates :domain, hostname: {allow_slash: true}
         | 
| 191 | 
            +
             | 
| 150 192 | 
             
                # allows 'a.example.com', but not 'example.com'
         | 
| 151 193 | 
             
                validates :domain, hostname: {segments: 3..100}
         | 
| 152 194 |  | 
| @@ -158,6 +200,34 @@ TLD, so as to not fail as ICANN continues to add TLDs. | |
| 158 200 | 
             
                # use 4 or 6 for ipv4 or ipv6 only
         | 
| 159 201 |  | 
| 160 202 |  | 
| 203 | 
            +
            ## IP address validator ##
         | 
| 204 | 
            +
             | 
| 205 | 
            +
            Ensures an attribute is generally formatted as a IP or IP block.
         | 
| 206 | 
            +
             | 
| 207 | 
            +
                # allows '1.2.3.4' or '::1'
         | 
| 208 | 
            +
                validates :ip, ipaddr: true
         | 
| 209 | 
            +
             | 
| 210 | 
            +
                # allows '1.2.3.0/24' or '2001:db8::/64'
         | 
| 211 | 
            +
                validates :cidr, ipaddr: {allow_block: true}
         | 
| 212 | 
            +
             | 
| 213 | 
            +
                # if an ip block, the attribute must be fully contained within an allowed block.
         | 
| 214 | 
            +
                # allows '10.0.0.1' and '10.0.0.0/24', but not '10.0.0.0/15'
         | 
| 215 | 
            +
                validates :private_ip, ipaddr: {
         | 
| 216 | 
            +
                  allow_block: true,
         | 
| 217 | 
            +
                  within: [IPAddr.new('10.0.0.0/16'), '127.0.0.1']
         | 
| 218 | 
            +
                    # allowed IPs and blocks may be IPAddrs or Strings
         | 
| 219 | 
            +
                }
         | 
| 220 | 
            +
             | 
| 221 | 
            +
                # the inverse of :within
         | 
| 222 | 
            +
                validates :public_ip6, ipaddr: {without: ['fc00::/7']]}
         | 
| 223 | 
            +
             | 
| 224 | 
            +
                # :within and :without may also be procs or method names
         | 
| 225 | 
            +
                validates :ip, ipaddr: {
         | 
| 226 | 
            +
                  within: :some_method,
         | 
| 227 | 
            +
                  without: ->(record){ ... }
         | 
| 228 | 
            +
                }
         | 
| 229 | 
            +
             | 
| 230 | 
            +
             | 
| 161 231 | 
             
            ## Ordering validators ##
         | 
| 162 232 |  | 
| 163 233 | 
             
            Ensures two attribute values maintain a relative order to one another. This is
         | 
| @@ -183,9 +253,9 @@ Always skips over nil values; use `:presence` to validate those. | |
| 183 253 |  | 
| 184 254 | 
             
            ## URL validator ##
         | 
| 185 255 |  | 
| 186 | 
            -
             | 
| 256 | 
            +
            Ensures an attribute is generally formatted as a URL. If `addressable/uri` is
         | 
| 187 257 | 
             
            already loaded, it will be used to parse IDN's. Additionally, allowed schemes
         | 
| 188 | 
            -
            can be specified; they default to ['http','https'] | 
| 258 | 
            +
            can be specified; they default to `['http','https']`.
         | 
| 189 259 |  | 
| 190 260 | 
             
                validates :website, url: true
         | 
| 191 261 | 
             
                validates :secure_url, url: {scheme: 'https'}
         | 
| @@ -205,8 +275,8 @@ can be specified; they default to ['http','https']. | |
| 205 275 |  | 
| 206 276 | 
             
            ## Write Once validator ##
         | 
| 207 277 |  | 
| 208 | 
            -
             | 
| 209 | 
            -
            for this. | 
| 278 | 
            +
            Ensures that once a value is written, it becomes readonly. There are a few
         | 
| 279 | 
            +
            uses for this.
         | 
| 210 280 |  | 
| 211 281 | 
             
            The first is as an equivalent to `attr_readonly :user_id` except that it also
         | 
| 212 282 | 
             
            produces a validation error instead of silently ignoring the change as
         | 
| @@ -221,6 +291,10 @@ sorts. | |
| 221 291 |  | 
| 222 292 | 
             
                validates :user_id, allow_nil: true, write_once: true
         | 
| 223 293 |  | 
| 294 | 
            +
            The third use is to allow a nil value, and treat the nil also as write-once.
         | 
| 295 | 
            +
             | 
| 296 | 
            +
                validates :source, write_once: {immutable_nil: true}
         | 
| 297 | 
            +
             | 
| 224 298 |  | 
| 225 299 | 
             
            ## Error messages
         | 
| 226 300 |  | 
| @@ -232,6 +306,9 @@ Default messages are as follows: | |
| 232 306 | 
             
                    messages:
         | 
| 233 307 | 
             
                      invalid_email: "is an invalid email"
         | 
| 234 308 | 
             
                      invalid_hostname: "is an invalid hostname"
         | 
| 309 | 
            +
                      invalid_ip: "is an invalid IP"
         | 
| 310 | 
            +
                      ip_not_allowed: "is not an allowed IP"
         | 
| 311 | 
            +
                      single_ip_required: "must be a single IP"
         | 
| 235 312 | 
             
                      invalid_url: "is an invalid URL"
         | 
| 236 313 | 
             
                      unchangeable: "cannot be changed"
         | 
| 237 314 | 
             
                      before: "must be before %{attribute2}"
         | 
    
        data/lib/can_has_validations.rb
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            require 'active_model/validations'
         | 
| 2 2 |  | 
| 3 | 
            -
            %w(array email existence grandparent hostname ordering url write_once).each do |validator|
         | 
| 3 | 
            +
            %w(array email existence grandparent hash_keys hash_values hostname ipaddr ordering url write_once).each do |validator|
         | 
| 4 4 | 
             
              require "can_has_validations/validators/#{validator}_validator"
         | 
| 5 5 | 
             
            end
         | 
| 6 6 |  | 
| @@ -3,8 +3,10 @@ en: | |
| 3 3 | 
             
                messages:
         | 
| 4 4 | 
             
                  invalid_email: "is an invalid email"
         | 
| 5 5 | 
             
                  invalid_hostname: "is an invalid hostname"
         | 
| 6 | 
            +
                  invalid_ip: "is an invalid IP"
         | 
| 7 | 
            +
                  ip_not_allowed: "is not an allowed IP"
         | 
| 8 | 
            +
                  single_ip_required: "must be a single IP"
         | 
| 6 9 | 
             
                  invalid_url: "is an invalid URL"
         | 
| 7 10 | 
             
                  unchangeable: "cannot be changed"
         | 
| 8 11 | 
             
                  before: "must be before %{attribute2}"
         | 
| 9 12 | 
             
                  after: "must be after %{attribute2}"
         | 
| 10 | 
            -
                  
         | 
| @@ -0,0 +1,12 @@ | |
| 1 | 
            +
            es:
         | 
| 2 | 
            +
              errors:
         | 
| 3 | 
            +
                messages:
         | 
| 4 | 
            +
                  invalid_email: "es un correo electrónico no válido"
         | 
| 5 | 
            +
                  invalid_hostname: "es un nombre de sistema no válido"
         | 
| 6 | 
            +
                  invalid_ip: "es una IP no válida"
         | 
| 7 | 
            +
                  ip_not_allowed: "no es una IP permitida"
         | 
| 8 | 
            +
                  single_ip_required: "debe ser solo una IP"
         | 
| 9 | 
            +
                  invalid_url: "es una URL no válida"
         | 
| 10 | 
            +
                  unchangeable: "no se puede cambiar"
         | 
| 11 | 
            +
                  before: "debe ser antes de %{attribute2}"
         | 
| 12 | 
            +
                  after: "debe ser después de %{attribute2}"
         | 
| @@ -19,6 +19,18 @@ | |
| 19 19 | 
             
            #       multiple_errors: true,
         | 
| 20 20 | 
             
            #       format: /\A[^aeiou]*\z/
         | 
| 21 21 | 
             
            #     }
         | 
| 22 | 
            +
            #
         | 
| 23 | 
            +
            # the :if, :unless, and :on conditionals are not supported on sub-validators,
         | 
| 24 | 
            +
            # but do work as normal on the :array validator itself.
         | 
| 25 | 
            +
            #
         | 
| 26 | 
            +
            #   validates :permissions, if: :this_condition_works,
         | 
| 27 | 
            +
            #     array: {
         | 
| 28 | 
            +
            #       if: :this_condition_applies_to_permissions_but_not_each_element,
         | 
| 29 | 
            +
            #       inclusion: {
         | 
| 30 | 
            +
            #         in: %w(one two),
         | 
| 31 | 
            +
            #         unless: :conditions_on_subvalidators_are_ignored
         | 
| 32 | 
            +
            #       }
         | 
| 33 | 
            +
            #     }
         | 
| 22 34 |  | 
| 23 35 | 
             
            module ActiveModel
         | 
| 24 36 | 
             
              module Validations
         | 
| @@ -33,12 +45,17 @@ module ActiveModel | |
| 33 45 | 
             
                    defaults = @options.dup
         | 
| 34 46 | 
             
                    validations = defaults.slice!(*record_class.send(:_validates_default_keys), :attributes)
         | 
| 35 47 |  | 
| 36 | 
            -
                    raise ArgumentError, "You need to supply at least one  | 
| 48 | 
            +
                    raise ArgumentError, "You need to supply at least one validation for :#{kind}" if validations.empty?
         | 
| 37 49 |  | 
| 38 50 | 
             
                    defaults[:attributes] = attributes
         | 
| 39 51 |  | 
| 40 52 | 
             
                    @validators = validations.map do |key, sub_options|
         | 
| 41 53 | 
             
                      next unless sub_options
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                      if (cond_keys = _parse_validates_options(sub_options).keys & %i(if on unless)).any?
         | 
| 56 | 
            +
                        raise ArgumentError, ":#{kind} does not support conditionals on sub-validators - found on #{key}: #{cond_keys.map(&:inspect).join(', ')}"
         | 
| 57 | 
            +
                      end
         | 
| 58 | 
            +
             | 
| 42 59 | 
             
                      key = "#{key.to_s.camelize}Validator"
         | 
| 43 60 |  | 
| 44 61 | 
             
                      begin
         | 
| @@ -47,7 +64,7 @@ module ActiveModel | |
| 47 64 | 
             
                        raise ArgumentError, "Unknown validator: '#{key}'"
         | 
| 48 65 | 
             
                      end
         | 
| 49 66 |  | 
| 50 | 
            -
                      klass.new(defaults.merge(_parse_validates_options(sub_options)))
         | 
| 67 | 
            +
                      klass.new(defaults.merge(_parse_validates_options(sub_options)).except(:if, :on, :unless))
         | 
| 51 68 | 
             
                    end
         | 
| 52 69 | 
             
                  end
         | 
| 53 70 |  | 
| @@ -56,7 +73,7 @@ module ActiveModel | |
| 56 73 | 
             
                      error_count = count_errors(record)
         | 
| 57 74 |  | 
| 58 75 | 
             
                      Array(array_values).each do |value|
         | 
| 59 | 
            -
                        validator | 
| 76 | 
            +
                        validate_one(validator, record, attribute, value)
         | 
| 60 77 |  | 
| 61 78 | 
             
                        # to avoid repeating error messages, stop after a single error
         | 
| 62 79 | 
             
                        unless validator.options[:multiple_errors]
         | 
| @@ -66,14 +83,21 @@ module ActiveModel | |
| 66 83 | 
             
                    end
         | 
| 67 84 | 
             
                  end
         | 
| 68 85 |  | 
| 86 | 
            +
                  def validate_one(validator, record, attribute, value)
         | 
| 87 | 
            +
                    unless validator.is_a?(ExistenceValidator)
         | 
| 88 | 
            +
                      return if (value.nil? && validator.options[:allow_nil]) || (value.blank? && validator.options[:allow_blank])
         | 
| 89 | 
            +
                    end
         | 
| 90 | 
            +
                    validator.validate_each(record, attribute, value)
         | 
| 91 | 
            +
                  end
         | 
| 92 | 
            +
             | 
| 69 93 |  | 
| 70 94 | 
             
                  private
         | 
| 71 95 |  | 
| 72 96 | 
             
                  def count_errors(record)
         | 
| 73 | 
            -
                     | 
| 74 | 
            -
                    record.errors.messages.sum{|key, val| val.blank? ? 0 : val.size }
         | 
| 97 | 
            +
                    record.errors.count
         | 
| 75 98 | 
             
                  end
         | 
| 76 99 |  | 
| 100 | 
            +
                  # copied from active_model/validations/validates.rb
         | 
| 77 101 | 
             
                  def _parse_validates_options(options)
         | 
| 78 102 | 
             
                    case options
         | 
| 79 103 | 
             
                    when TrueClass
         | 
| @@ -13,7 +13,7 @@ module ActiveModel::Validations | |
| 13 13 |  | 
| 14 14 | 
             
                def validate_each(record, attribute, value)
         | 
| 15 15 | 
             
                  unless email_valid?(value)
         | 
| 16 | 
            -
                    record.errors.add(attribute, :invalid_email, options.merge(value: value))
         | 
| 16 | 
            +
                    record.errors.add(attribute, :invalid_email, **options.merge(value: value))
         | 
| 17 17 | 
             
                  end
         | 
| 18 18 | 
             
                end
         | 
| 19 19 |  | 
| @@ -16,11 +16,12 @@ module ActiveModel::Validations | |
| 16 16 | 
             
                    if cousin.nil?
         | 
| 17 17 | 
             
                      options[:allow_nil]
         | 
| 18 18 | 
             
                    else
         | 
| 19 | 
            -
                      association | 
| 19 | 
            +
                      association &&
         | 
| 20 | 
            +
                        association.send(options[:parent]) == cousin.send(options[:parent])
         | 
| 20 21 | 
             
                    end
         | 
| 21 22 | 
             
                  end
         | 
| 22 23 | 
             
                  unless all_match
         | 
| 23 | 
            -
                    record.errors.add(attribute, :invalid, options.except(:allow_nil, :parent, :scope))
         | 
| 24 | 
            +
                    record.errors.add(attribute, :invalid, **options.except(:allow_nil, :parent, :scope))
         | 
| 24 25 | 
             
                  end
         | 
| 25 26 | 
             
                end
         | 
| 26 27 | 
             
              end
         | 
| @@ -0,0 +1,30 @@ | |
| 1 | 
            +
            # validates each key of a hash attribute
         | 
| 2 | 
            +
            #
         | 
| 3 | 
            +
            # by default only allows the first error per validator, regardless of how many
         | 
| 4 | 
            +
            # keys fail validation. this improves performance and avoids a bunch of
         | 
| 5 | 
            +
            # repeating error messages.
         | 
| 6 | 
            +
            # use `multiple_errors: true` on :hash_keys or a single sub-validator to
         | 
| 7 | 
            +
            # enable the full set of errors. this is potentially useful if each error
         | 
| 8 | 
            +
            # message will vary based upon each hash key.
         | 
| 9 | 
            +
            #
         | 
| 10 | 
            +
            # the :if, :unless, and :on conditionals are not supported on sub-validators,
         | 
| 11 | 
            +
            # but do work as normal on the :hash_keys validator itself.
         | 
| 12 | 
            +
            #
         | 
| 13 | 
            +
            # usage:
         | 
| 14 | 
            +
            #   validates :subjects,
         | 
| 15 | 
            +
            #     hash_keys: {
         | 
| 16 | 
            +
            #       format: /\A[a-z]+\z/,
         | 
| 17 | 
            +
            #       # multiple_errors: true
         | 
| 18 | 
            +
            #     }
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            module ActiveModel
         | 
| 21 | 
            +
              module Validations
         | 
| 22 | 
            +
                class HashKeysValidator < ArrayValidator
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def validate_each(record, attribute, hash)
         | 
| 25 | 
            +
                    super(record, attribute, Hash(hash).keys)
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
            end
         | 
| @@ -0,0 +1,55 @@ | |
| 1 | 
            +
            # validates each value of a hash attribute
         | 
| 2 | 
            +
            #
         | 
| 3 | 
            +
            # by default only allows the first error per validator, regardless of how many
         | 
| 4 | 
            +
            # values fail validation. this improves performance and avoids a bunch of
         | 
| 5 | 
            +
            # repeating error messages.
         | 
| 6 | 
            +
            # use `multiple_errors: true` on :hash_values or a single sub-validator to
         | 
| 7 | 
            +
            # enable the full set of errors. this is potentially useful if each error
         | 
| 8 | 
            +
            # message will vary based upon each hash value.
         | 
| 9 | 
            +
            #
         | 
| 10 | 
            +
            # the :if, :unless, and :on conditionals are not supported on sub-validators,
         | 
| 11 | 
            +
            # but do work as normal on the :hash_values validator itself.
         | 
| 12 | 
            +
            #
         | 
| 13 | 
            +
            # usage:
         | 
| 14 | 
            +
            #   validates :subjects,
         | 
| 15 | 
            +
            #     hash_values: {
         | 
| 16 | 
            +
            #       length: 3..100,
         | 
| 17 | 
            +
            #       # multiple_errors: true
         | 
| 18 | 
            +
            #     }
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            module ActiveModel
         | 
| 21 | 
            +
              module Validations
         | 
| 22 | 
            +
                class HashValuesValidator < ArrayValidator
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def initialize(options)
         | 
| 25 | 
            +
                    record_class = options[:class]
         | 
| 26 | 
            +
                    super
         | 
| 27 | 
            +
                    record_class.include HashValidatorKey
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def validate_each(record, attribute, hash)
         | 
| 31 | 
            +
                    super(record, attribute, Array(Hash(hash)))
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  def validate_one(validator, record, attribute, key_and_value)
         | 
| 35 | 
            +
                    key, value = key_and_value
         | 
| 36 | 
            +
                    record.hash_validator_key = key
         | 
| 37 | 
            +
                    super(validator, record, attribute, value)
         | 
| 38 | 
            +
                  ensure
         | 
| 39 | 
            +
                    record.hash_validator_key = nil
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
             | 
| 43 | 
            +
                  module HashValidatorKey
         | 
| 44 | 
            +
                    def hash_validator_key
         | 
| 45 | 
            +
                      @_hash_validator_key
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    def hash_validator_key=(v)
         | 
| 49 | 
            +
                      @_hash_validator_key = v
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
            end
         | 
| @@ -18,6 +18,8 @@ | |
| 18 18 | 
             
            #       allows '*.example.com'
         | 
| 19 19 | 
             
            #     validates :domain, hostname: {allow_underscore: true}
         | 
| 20 20 | 
             
            #       allows '_abc.example.com'
         | 
| 21 | 
            +
            #     validates :domain, hostname: {allow_slash: true}
         | 
| 22 | 
            +
            #       allows '4.0/25.3.2.1.example.com'  # rfc2317
         | 
| 21 23 | 
             
            #     validates :domain, hostname: {segments: 3..100}
         | 
| 22 24 | 
             
            #       allows 'a.example.com', but not 'example.com'
         | 
| 23 25 | 
             
            #     validates :domain, hostname: {allow_ip: true}  # or 4 or 6 for ipv4 or ipv6 only
         | 
| @@ -30,9 +32,9 @@ require 'resolv' | |
| 30 32 | 
             
            module ActiveModel::Validations
         | 
| 31 33 | 
             
              class HostnameValidator < ActiveModel::EachValidator
         | 
| 32 34 |  | 
| 33 | 
            -
                LABEL_REGEXP =  | 
| 34 | 
            -
                FINAL_LABEL_REGEXP =  | 
| 35 | 
            -
                RESERVED_OPTIONS = %i(allow_ip allow_underscore allow_wildcard)
         | 
| 35 | 
            +
                LABEL_REGEXP = %r{\A([a-zA-Z0-9_]([a-zA-Z0-9_/-]+)?)?[a-zA-Z0-9]\z}
         | 
| 36 | 
            +
                FINAL_LABEL_REGEXP = %r{\A(xn--[a-zA-Z0-9]{2,}|[a-zA-Z]{2,})\z}
         | 
| 37 | 
            +
                RESERVED_OPTIONS = %i(allow_ip allow_slash allow_underscore allow_wildcard)
         | 
| 36 38 |  | 
| 37 39 | 
             
                def validate_each(record, attribute, value)
         | 
| 38 40 | 
             
                  case options[:allow_ip]
         | 
| @@ -55,6 +57,7 @@ module ActiveModel::Validations | |
| 55 57 | 
             
                  is_valid &&= value.length <= 255
         | 
| 56 58 | 
             
                  is_valid &&= value !~ /\.\./
         | 
| 57 59 | 
             
                  is_valid &&= value !~ /_/ unless options[:allow_underscore]
         | 
| 60 | 
            +
                  is_valid &&= value !~ %r{/} unless options[:allow_slash]
         | 
| 58 61 | 
             
                  is_valid &&= labels.size.in? segments
         | 
| 59 62 | 
             
                  labels.each_with_index do |label, idx|
         | 
| 60 63 | 
             
                    is_valid &&= label.length <= 63
         | 
| @@ -68,7 +71,7 @@ module ActiveModel::Validations | |
| 68 71 | 
             
                  end
         | 
| 69 72 |  | 
| 70 73 | 
             
                  unless is_valid
         | 
| 71 | 
            -
                    record.errors.add(attribute, :invalid_hostname, options.except(*RESERVED_OPTIONS).merge!(value: value))
         | 
| 74 | 
            +
                    record.errors.add(attribute, :invalid_hostname, **options.except(*RESERVED_OPTIONS).merge!(value: value))
         | 
| 72 75 | 
             
                  end
         | 
| 73 76 | 
             
                end
         | 
| 74 77 |  | 
| @@ -0,0 +1,91 @@ | |
| 1 | 
            +
            # Ensure an attribute is generally formatted as a IP or IP block.
         | 
| 2 | 
            +
            # eg: validates :ip, ipaddr: true
         | 
| 3 | 
            +
            #     validates :cidr, ipaddr: {allow_block: true}
         | 
| 4 | 
            +
            #     validates :private_ip, ipaddr: {within: [IPAddr.new('10.0.0.0/8'), '127.0.0.1']}
         | 
| 5 | 
            +
            #       ip must be within any one of the provided ips/blocks
         | 
| 6 | 
            +
            #       if ip is block, it must be fully contained within any one of the provided blocks
         | 
| 7 | 
            +
            #     validates :public_ip6, ipaddr: {without: ['fc00::/7']]}
         | 
| 8 | 
            +
            #       ip must be outside all of the provided ips/blocks
         | 
| 9 | 
            +
            #       if ip is block, it must be fully outside all of the provided blocks
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            require 'ipaddr'
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            module ActiveModel::Validations
         | 
| 14 | 
            +
              class IpaddrValidator < ActiveModel::EachValidator
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def initialize(options)
         | 
| 17 | 
            +
                  options[:within]  = normalize_within options[:within], :within
         | 
| 18 | 
            +
                  options[:without] = normalize_within options[:without], :without
         | 
| 19 | 
            +
                  super
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def validate_each(record, attribute, value)
         | 
| 23 | 
            +
                  allowed_ips    = resolve_array record, options[:within]
         | 
| 24 | 
            +
                  disallowed_ips = resolve_array record, options[:without]
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  ip = case value
         | 
| 27 | 
            +
                    when IPAddr
         | 
| 28 | 
            +
                      ip
         | 
| 29 | 
            +
                    when String
         | 
| 30 | 
            +
                      IPAddr.new(value) rescue nil
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                  unless ip
         | 
| 33 | 
            +
                    record.errors.add(attribute, :invalid_ip, **options.merge(value: value))
         | 
| 34 | 
            +
                    return
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  if !options[:allow_block] && (ip.ipv4? && ip.prefix!=32 or ip.ipv6? && ip.prefix!=128)
         | 
| 38 | 
            +
                    record.errors.add(attribute, :single_ip_required, **options.merge(value: value))
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
                  if allowed_ips && allowed_ips.none?{|blk| blk.include? ip}
         | 
| 41 | 
            +
                    record.errors.add(attribute, :ip_not_allowed, **options.merge(value: value))
         | 
| 42 | 
            +
                  elsif disallowed_ips && disallowed_ips.any?{|blk| blk.include? ip}
         | 
| 43 | 
            +
                    record.errors.add(attribute, :ip_not_allowed, **options.merge(value: value))
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
             | 
| 48 | 
            +
                private
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                def normalize_within(val, key)
         | 
| 51 | 
            +
                  if val.nil? || val.respond_to?(:call) || val.is_a?(Symbol)
         | 
| 52 | 
            +
                    val
         | 
| 53 | 
            +
                  else
         | 
| 54 | 
            +
                    Array(val).flatten.map do |i|
         | 
| 55 | 
            +
                      case i
         | 
| 56 | 
            +
                      when IPAddr
         | 
| 57 | 
            +
                        i
         | 
| 58 | 
            +
                      when String
         | 
| 59 | 
            +
                        IPAddr.new i
         | 
| 60 | 
            +
                      else
         | 
| 61 | 
            +
                        raise "Unexpected value for #{key.inspect} : #{i}"
         | 
| 62 | 
            +
                      end
         | 
| 63 | 
            +
                    end
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                def resolve_array(record, val)
         | 
| 68 | 
            +
                  res = if val.respond_to?(:call)
         | 
| 69 | 
            +
                    val.call(record)
         | 
| 70 | 
            +
                  elsif val.is_a?(Symbol)
         | 
| 71 | 
            +
                    record.send(val)
         | 
| 72 | 
            +
                  else
         | 
| 73 | 
            +
                    val
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
              end
         | 
| 78 | 
            +
            end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
            # tests for & fixes broken IPAddr <= 1.2.2
         | 
| 81 | 
            +
            if IPAddr.new('192.168.2.0/32').include? '192.168.2.0/24'
         | 
| 82 | 
            +
              # warn 'IPAddr <= 1.2.2 is broken; monkey-patching'
         | 
| 83 | 
            +
              class IPAddr
         | 
| 84 | 
            +
                def include?(other)
         | 
| 85 | 
            +
                  range = to_range
         | 
| 86 | 
            +
                  other = coerce_other(other).to_range
         | 
| 87 | 
            +
                  range.begin <= other.begin && range.end >= other.end
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
                alias === include?
         | 
| 90 | 
            +
              end
         | 
| 91 | 
            +
            end
         | 
| @@ -18,7 +18,7 @@ module ActiveModel::Validations | |
| 18 18 | 
             
                    next unless value && greater
         | 
| 19 19 | 
             
                    unless value < greater
         | 
| 20 20 | 
             
                      attr2 = attr_name.respond_to?(:call) ? 'it is' : record.class.human_attribute_name(attr_name)
         | 
| 21 | 
            -
                      record.errors.add(attribute, :before, options.except(:before).merge!(attribute2: attr2, value: value))
         | 
| 21 | 
            +
                      record.errors.add(attribute, :before, **options.except(:before).merge!(attribute2: attr2, value: value))
         | 
| 22 22 | 
             
                    end
         | 
| 23 23 | 
             
                  end
         | 
| 24 24 | 
             
                end
         | 
| @@ -33,7 +33,7 @@ module ActiveModel::Validations | |
| 33 33 | 
             
                    next unless value && lesser
         | 
| 34 34 | 
             
                    unless value > lesser
         | 
| 35 35 | 
             
                      attr2 = attr_name.respond_to?(:call) ? 'it is' : record.class.human_attribute_name(attr_name)
         | 
| 36 | 
            -
                      record.errors.add(attribute, :after, options.except(:after).merge!(attribute2: attr2, value: value))
         | 
| 36 | 
            +
                      record.errors.add(attribute, :after, **options.except(:after).merge!(attribute2: attr2, value: value))
         | 
| 37 37 | 
             
                    end
         | 
| 38 38 | 
             
                  end
         | 
| 39 39 | 
             
                end
         | 
| @@ -22,7 +22,7 @@ module ActiveModel::Validations | |
| 22 22 | 
             
                    u2 = u = URI.parse(value) rescue nil
         | 
| 23 23 | 
             
                  end
         | 
| 24 24 | 
             
                  if !u || !u2 || u.relative? || allowed_schemes.exclude?(u.scheme)
         | 
| 25 | 
            -
                    record.errors.add(attribute, :invalid_url, options.merge(value: value, scheme: allowed_schemes))
         | 
| 25 | 
            +
                    record.errors.add(attribute, :invalid_url, **options.merge(value: value, scheme: allowed_schemes))
         | 
| 26 26 | 
             
                  end
         | 
| 27 27 | 
             
                end
         | 
| 28 28 | 
             
              end
         | 
| @@ -12,15 +12,21 @@ module ActiveModel::Validations | |
| 12 12 | 
             
                #   nil to be allowed. prevent this.
         | 
| 13 13 | 
             
                def validate(record)
         | 
| 14 14 | 
             
                  attributes.each do |attribute|
         | 
| 15 | 
            -
                     | 
| 16 | 
            -
                    validate_each(record, attribute, value)
         | 
| 15 | 
            +
                    validate_each(record, attribute, nil)
         | 
| 17 16 | 
             
                  end
         | 
| 18 17 | 
             
                end
         | 
| 19 18 |  | 
| 20 | 
            -
                def validate_each(record, attribute,  | 
| 21 | 
            -
                   | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 19 | 
            +
                def validate_each(record, attribute, _)
         | 
| 20 | 
            +
                  return unless record.persisted?
         | 
| 21 | 
            +
                  if !record.respond_to?("#{attribute}_changed?") && record.respond_to?("#{attribute}_id_changed?")
         | 
| 22 | 
            +
                    attr2 = "#{attribute}_id"
         | 
| 23 | 
            +
                  else
         | 
| 24 | 
            +
                    attr2 = attribute
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
                  if record.send("#{attr2}_changed?")
         | 
| 27 | 
            +
                    if options[:immutable_nil] || !record.send("#{attr2}_was").nil?
         | 
| 28 | 
            +
                      value = record.read_attribute_for_validation(attribute)
         | 
| 29 | 
            +
                      record.errors.add(attribute, :unchangeable, **options.except(:immutable_nil).merge!(value: value))
         | 
| 24 30 | 
             
                    end
         | 
| 25 31 | 
             
                  end
         | 
| 26 32 | 
             
                end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: can_has_validations
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 1. | 
| 4 | 
            +
              version: 1.3.1
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - thomas morgan
         | 
| 8 | 
            -
            autorequire: | 
| 8 | 
            +
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 11 | 
            +
            date: 2021-05-20 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: rails
         | 
| @@ -19,7 +19,7 @@ dependencies: | |
| 19 19 | 
             
                    version: '5.0'
         | 
| 20 20 | 
             
                - - "<"
         | 
| 21 21 | 
             
                  - !ruby/object:Gem::Version
         | 
| 22 | 
            -
                    version: '6. | 
| 22 | 
            +
                    version: '6.2'
         | 
| 23 23 | 
             
              type: :runtime
         | 
| 24 24 | 
             
              prerelease: false
         | 
| 25 25 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| @@ -29,7 +29,7 @@ dependencies: | |
| 29 29 | 
             
                    version: '5.0'
         | 
| 30 30 | 
             
                - - "<"
         | 
| 31 31 | 
             
                  - !ruby/object:Gem::Version
         | 
| 32 | 
            -
                    version: '6. | 
| 32 | 
            +
                    version: '6.2'
         | 
| 33 33 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 34 34 | 
             
              name: sqlite3
         | 
| 35 35 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -44,7 +44,8 @@ dependencies: | |
| 44 44 | 
             
                - - ">="
         | 
| 45 45 | 
             
                  - !ruby/object:Gem::Version
         | 
| 46 46 | 
             
                    version: '0'
         | 
| 47 | 
            -
            description: Assorted Rails 5.x-6.x validators | 
| 47 | 
            +
            description: 'Assorted Rails 5.x-6.x validators: Array, Email, Existence, Grandparent,
         | 
| 48 | 
            +
              Hostname, IP address, Ordering, URL, Write Once'
         | 
| 48 49 | 
             
            email:
         | 
| 49 50 | 
             
            - tm@iprog.com
         | 
| 50 51 | 
             
            executables: []
         | 
| @@ -56,11 +57,15 @@ files: | |
| 56 57 | 
             
            - Rakefile
         | 
| 57 58 | 
             
            - lib/can_has_validations.rb
         | 
| 58 59 | 
             
            - lib/can_has_validations/locale/en.yml
         | 
| 60 | 
            +
            - lib/can_has_validations/locale/es.yml
         | 
| 59 61 | 
             
            - lib/can_has_validations/validators/array_validator.rb
         | 
| 60 62 | 
             
            - lib/can_has_validations/validators/email_validator.rb
         | 
| 61 63 | 
             
            - lib/can_has_validations/validators/existence_validator.rb
         | 
| 62 64 | 
             
            - lib/can_has_validations/validators/grandparent_validator.rb
         | 
| 65 | 
            +
            - lib/can_has_validations/validators/hash_keys_validator.rb
         | 
| 66 | 
            +
            - lib/can_has_validations/validators/hash_values_validator.rb
         | 
| 63 67 | 
             
            - lib/can_has_validations/validators/hostname_validator.rb
         | 
| 68 | 
            +
            - lib/can_has_validations/validators/ipaddr_validator.rb
         | 
| 64 69 | 
             
            - lib/can_has_validations/validators/ordering_validator.rb
         | 
| 65 70 | 
             
            - lib/can_has_validations/validators/url_validator.rb
         | 
| 66 71 | 
             
            - lib/can_has_validations/validators/write_once_validator.rb
         | 
| @@ -98,9 +103,10 @@ files: | |
| 98 103 | 
             
            - test/dummy/script/rails
         | 
| 99 104 | 
             
            - test/test_helper.rb
         | 
| 100 105 | 
             
            homepage: https://github.com/zarqman/can_has_validations
         | 
| 101 | 
            -
            licenses: | 
| 106 | 
            +
            licenses:
         | 
| 107 | 
            +
            - MIT
         | 
| 102 108 | 
             
            metadata: {}
         | 
| 103 | 
            -
            post_install_message: | 
| 109 | 
            +
            post_install_message:
         | 
| 104 110 | 
             
            rdoc_options: []
         | 
| 105 111 | 
             
            require_paths:
         | 
| 106 112 | 
             
            - lib
         | 
| @@ -115,8 +121,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 115 121 | 
             
                - !ruby/object:Gem::Version
         | 
| 116 122 | 
             
                  version: '0'
         | 
| 117 123 | 
             
            requirements: []
         | 
| 118 | 
            -
            rubygems_version: 3.0. | 
| 119 | 
            -
            signing_key: | 
| 124 | 
            +
            rubygems_version: 3.0.9
         | 
| 125 | 
            +
            signing_key:
         | 
| 120 126 | 
             
            specification_version: 4
         | 
| 121 127 | 
             
            summary: Assorted Rails 5.x-6.x validators
         | 
| 122 128 | 
             
            test_files:
         |