can_has_validations 1.2.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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