can_has_validations 1.2.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2c82e1883d7ee1fa1dd3a098f4f9bfd716b63321cec4b2aa4ff873351144f9fc
4
- data.tar.gz: b4ab341c9a388e8e3bdcd8f10fbaa4ca1c2b7ab5f6db345ae3f7d84b65fc26b5
3
+ metadata.gz: 35a018491f2cdaaf42b335b1d161573555a41b10f3a01b3789e51afbc8b75a0e
4
+ data.tar.gz: acfa4a74d6709f9184fe46d24eef3fa36771c0f1a540fdbd1d75682b49e67bf8
5
5
  SHA512:
6
- metadata.gz: 23684cebc4fce328fa9d9333a48332f7070c354c7876b5f0ee878917e7fdd6b2ae1256b33b99e9ed086bc1a6a323c440c4746b93851e4734182d9783ff5e22a2
7
- data.tar.gz: '0941308278c6c9ea1a7b9b0a4b157b28e83bbe061a52f3dee0ffc2c9961e54f742fa7698516c02f5c8909795fe9297ec4093f744b323bb57a17c920dabaf4035'
6
+ metadata.gz: ea6a073102cae7965b1f430c169db660f16fcbb826271016a991fef4888401fbb0da206dcd030c61bdf4aab04100d934eaa6efbb76fb12f276f25a1ae30c6a04
7
+ data.tar.gz: 328d8fe59d6e527cc5373a1193654a689c3ff418573b4114fe358475ab5ce1c2a8e64e00ac85bd04cc5a55bd090c276cad4f19711c0919baae6895627958c455
data/README.md CHANGED
@@ -9,6 +9,8 @@ Validations provided:
9
9
  * Email
10
10
  * Existence
11
11
  * Grandparent
12
+ * Hash Keys
13
+ * Hash Values
12
14
  * Hostname
13
15
  * IP address
14
16
  * Ordering
@@ -135,6 +137,42 @@ or the database foreign key (`:user_id`). You can also use any other field. The
135
137
  test is merely that they match, not that they are associations.
136
138
 
137
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
+
138
176
  ## Hostname validator ##
139
177
 
140
178
  Ensures an attribute is generally formatted as a hostname. It allows for any
@@ -217,7 +255,7 @@ Always skips over nil values; use `:presence` to validate those.
217
255
 
218
256
  Ensures an attribute is generally formatted as a URL. If `addressable/uri` is
219
257
  already loaded, it will be used to parse IDN's. Additionally, allowed schemes
220
- can be specified; they default to ['http','https'].
258
+ can be specified; they default to `['http','https']`.
221
259
 
222
260
  validates :website, url: true
223
261
  validates :secure_url, url: {scheme: 'https'}
@@ -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 array validation" if validations.empty?
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.validate_each(record, attribute, value)
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,6 +83,13 @@ 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
 
@@ -73,6 +97,7 @@ module ActiveModel
73
97
  record.errors.count
74
98
  end
75
99
 
100
+ # copied from active_model/validations/validates.rb
76
101
  def _parse_validates_options(options)
77
102
  case options
78
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
 
@@ -21,7 +21,7 @@ module ActiveModel::Validations
21
21
  end
22
22
  end
23
23
  unless all_match
24
- record.errors.add(attribute, :invalid, options.except(:allow_nil, :parent, :scope))
24
+ record.errors.add(attribute, :invalid, **options.except(:allow_nil, :parent, :scope))
25
25
  end
26
26
  end
27
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
@@ -24,8 +24,8 @@
24
24
  # allows 'a.example.com', but not 'example.com'
25
25
  # validates :domain, hostname: {allow_ip: true} # or 4 or 6 for ipv4 or ipv6 only
26
26
  # allows '1.2.3.4' or 'a.example.com'
27
- # validates :subdomain, hostname: {skip_tld: true, segments: 1}
28
- # allows 'subdomain1'
27
+ # validates :subdomain, hostname: {skip_tld: true}
28
+ # allows 'subdomain1'; implies segments: 1..100 unless otherwise specified
29
29
 
30
30
  require 'resolv'
31
31
 
@@ -46,7 +46,7 @@ module ActiveModel::Validations
46
46
  return if value =~ Resolv::IPv4::Regex || value =~ Resolv::IPv6::Regex
47
47
  end
48
48
 
49
- segments = options[:segments] || (2..100)
49
+ segments = options[:segments] || (options[:skip_tld] ? 1..100 : 2..100)
50
50
  segments = segments..segments if segments.is_a?(Integer)
51
51
  if defined?(Addressable::IDNA)
52
52
  value &&= Addressable::IDNA.to_ascii(value)
@@ -63,6 +63,8 @@ module ActiveModel::Validations
63
63
  is_valid &&= label.length <= 63
64
64
  if !options[:skip_tld] && idx+1==labels.size
65
65
  is_valid &&= label =~ FINAL_LABEL_REGEXP
66
+ elsif options[:allow_wildcard]==:multi && idx==0
67
+ is_valid &&= %w(** *).include?(label) || label =~ LABEL_REGEXP
66
68
  elsif options[:allow_wildcard] && idx==0
67
69
  is_valid &&= label=='*' || label =~ LABEL_REGEXP
68
70
  else
@@ -71,7 +73,7 @@ module ActiveModel::Validations
71
73
  end
72
74
 
73
75
  unless is_valid
74
- record.errors.add(attribute, :invalid_hostname, options.except(*RESERVED_OPTIONS).merge!(value: value))
76
+ record.errors.add(attribute, :invalid_hostname, **options.except(*RESERVED_OPTIONS).merge!(value: value))
75
77
  end
76
78
  end
77
79
 
@@ -30,23 +30,30 @@ module ActiveModel::Validations
30
30
  IPAddr.new(value) rescue nil
31
31
  end
32
32
  unless ip
33
- record.errors.add(attribute, :invalid_ip, options.merge(value: value))
33
+ record.errors.add(attribute, :invalid_ip, **options.merge(value: value))
34
34
  return
35
35
  end
36
36
 
37
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))
38
+ record.errors.add(attribute, :single_ip_required, **options.merge(value: value))
39
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))
40
+ if allowed_ips && allowed_ips.none?{|blk| ip_within_block? ip, blk}
41
+ record.errors.add(attribute, :ip_not_allowed, **options.merge(value: value))
42
+ elsif disallowed_ips && disallowed_ips.any?{|blk| ip_within_block? ip, blk}
43
+ record.errors.add(attribute, :ip_not_allowed, **options.merge(value: value))
44
44
  end
45
45
  end
46
46
 
47
47
 
48
48
  private
49
49
 
50
+ def ip_within_block?(ip, blk)
51
+ return false unless ip.family == blk.family
52
+ ip = ip.to_range
53
+ blk = blk.to_range
54
+ ip.begin >= blk.begin && ip.end <= blk.end
55
+ end
56
+
50
57
  def normalize_within(val, key)
51
58
  if val.nil? || val.respond_to?(:call) || val.is_a?(Symbol)
52
59
  val
@@ -72,20 +79,9 @@ module ActiveModel::Validations
72
79
  else
73
80
  val
74
81
  end
82
+ # raise "#{val.inspect} did not resolve to an Array of IPAddr" unless res.is_a?(Array) && res.all?{|r| r.is_a?(IPAddr)}
83
+ # res
75
84
  end
76
85
 
77
86
  end
78
87
  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
@@ -26,7 +26,7 @@ module ActiveModel::Validations
26
26
  if record.send("#{attr2}_changed?")
27
27
  if options[:immutable_nil] || !record.send("#{attr2}_was").nil?
28
28
  value = record.read_attribute_for_validation(attribute)
29
- record.errors.add(attribute, :unchangeable, options.except(:immutable_nil).merge!(value: value))
29
+ record.errors.add(attribute, :unchangeable, **options.except(:immutable_nil).merge!(value: value))
30
30
  end
31
31
  end
32
32
  end
@@ -1,3 +1,3 @@
1
1
  module CanHasValidations
2
- VERSION = '1.2.1'
2
+ VERSION = '1.4.0'
3
3
  end
@@ -1,6 +1,6 @@
1
1
  require 'active_model/validations'
2
2
 
3
- %w(array email existence grandparent hostname ipaddr 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
 
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.2.1
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - thomas morgan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-17 00:00:00.000000000 Z
11
+ date: 2021-10-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -45,7 +45,7 @@ dependencies:
45
45
  - !ruby/object:Gem::Version
46
46
  version: '0'
47
47
  description: 'Assorted Rails 5.x-6.x validators: Array, Email, Existence, Grandparent,
48
- Hostname, IP address, Ordering, URL, Write Once'
48
+ Hash keys, Hash values, Hostname, IP address, Ordering, URL, Write Once'
49
49
  email:
50
50
  - tm@iprog.com
51
51
  executables: []
@@ -57,10 +57,13 @@ files:
57
57
  - Rakefile
58
58
  - lib/can_has_validations.rb
59
59
  - lib/can_has_validations/locale/en.yml
60
+ - lib/can_has_validations/locale/es.yml
60
61
  - lib/can_has_validations/validators/array_validator.rb
61
62
  - lib/can_has_validations/validators/email_validator.rb
62
63
  - lib/can_has_validations/validators/existence_validator.rb
63
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
64
67
  - lib/can_has_validations/validators/hostname_validator.rb
65
68
  - lib/can_has_validations/validators/ipaddr_validator.rb
66
69
  - lib/can_has_validations/validators/ordering_validator.rb
@@ -118,39 +121,39 @@ required_rubygems_version: !ruby/object:Gem::Requirement
118
121
  - !ruby/object:Gem::Version
119
122
  version: '0'
120
123
  requirements: []
121
- rubygems_version: 3.0.9
124
+ rubygems_version: 3.2.22
122
125
  signing_key:
123
126
  specification_version: 4
124
127
  summary: Assorted Rails 5.x-6.x validators
125
128
  test_files:
126
- - test/dummy/app/controllers/application_controller.rb
127
- - test/dummy/app/views/layouts/application.html.erb
129
+ - test/can_has_validations_test.rb
130
+ - test/dummy/README.rdoc
131
+ - test/dummy/Rakefile
128
132
  - test/dummy/app/assets/javascripts/application.js
129
133
  - test/dummy/app/assets/stylesheets/application.css
134
+ - test/dummy/app/controllers/application_controller.rb
130
135
  - test/dummy/app/helpers/application_helper.rb
131
- - test/dummy/config/routes.rb
132
- - test/dummy/config/locales/en.yml
133
- - test/dummy/config/environments/production.rb
134
- - test/dummy/config/environments/development.rb
135
- - test/dummy/config/environments/test.rb
136
- - test/dummy/config/environment.rb
136
+ - test/dummy/app/views/layouts/application.html.erb
137
137
  - test/dummy/config/application.rb
138
- - test/dummy/config/database.yml
139
138
  - test/dummy/config/boot.rb
139
+ - test/dummy/config/database.yml
140
+ - test/dummy/config/environment.rb
141
+ - test/dummy/config/environments/development.rb
142
+ - test/dummy/config/environments/production.rb
143
+ - test/dummy/config/environments/test.rb
140
144
  - test/dummy/config/initializers/backtrace_silencers.rb
145
+ - test/dummy/config/initializers/inflections.rb
141
146
  - test/dummy/config/initializers/mime_types.rb
147
+ - test/dummy/config/initializers/secret_token.rb
142
148
  - test/dummy/config/initializers/session_store.rb
143
149
  - test/dummy/config/initializers/wrap_parameters.rb
144
- - test/dummy/config/initializers/secret_token.rb
145
- - test/dummy/config/initializers/inflections.rb
150
+ - test/dummy/config/locales/en.yml
151
+ - test/dummy/config/routes.rb
146
152
  - test/dummy/config.ru
147
- - test/dummy/script/rails
148
- - test/dummy/Rakefile
149
- - test/dummy/public/favicon.ico
153
+ - test/dummy/log/test.log
154
+ - test/dummy/public/404.html
150
155
  - test/dummy/public/422.html
151
156
  - test/dummy/public/500.html
152
- - test/dummy/public/404.html
153
- - test/dummy/log/test.log
154
- - test/dummy/README.rdoc
155
- - test/can_has_validations_test.rb
157
+ - test/dummy/public/favicon.ico
158
+ - test/dummy/script/rails
156
159
  - test/test_helper.rb