can_has_validations 1.1.0 → 1.3.2

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: 4b8c22fcbf2964d57091e6b566e5d05a1b501f286722ae26e4c63be5d42989f9
4
- data.tar.gz: c2cdd19af9fa8459007d506f93e4dec5b1311c173d4fdf087c7438a1d387d97f
3
+ metadata.gz: 3e81561f7a3d00cd1e760d29b9e529d51eb841e5b09bc91832fb4b4f0dbc71a1
4
+ data.tar.gz: 4ddd43af1f9f5397185d491ee7f38191f2c00bcc15a0a2c78b339ad88ddbd725
5
5
  SHA512:
6
- metadata.gz: 115570baed1e6a8061612de3f6428599ab4b76afab032756db8a7df5d5aa8bf8ec76f91d52849b5a28b154349965028c5f295350e4fd8f62d86b7f298e046cf2
7
- data.tar.gz: fa601d54efefa5ee3d83299b9838ba6684450009ef3f782e3904de6405ea6ecd05869e79e89daadf42904f8cfe8c791a2f397a1fb24ec08cd9ff1ced1bd269a9
6
+ metadata.gz: 30e3b36920d45b0d8e9b0d12ef404b03eb838aa12b905e462b38b407c44c23a91db19f21c7918c396982f69f4afd1afed5585ae795d6d32d4ab93d12aede28a5
7
+ data.tar.gz: 668d9ad8cccfa46754a67d7648f046f36a708c67ff98dcfde4c9392f071446cbbfb365211de82370833cbc92cb25ba118eea18002bbdec8ff204b4ef87e5354f
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2012-2020 thomas morgan
1
+ Copyright 2012-2021 thomas morgan
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
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'}
@@ -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
 
@@ -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,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
- # more efficient than calling record.errors.size
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
 
@@ -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
@@ -71,7 +71,7 @@ module ActiveModel::Validations
71
71
  end
72
72
 
73
73
  unless is_valid
74
- 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))
75
75
  end
76
76
  end
77
77
 
@@ -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
@@ -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
- value = record.read_attribute_for_validation(attribute)
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, value)
21
- if record.persisted? && record.send("#{attribute}_changed?")
22
- if options[:immutable_nil] || !record.send("#{attribute}_was").nil?
23
- record.errors.add(attribute, :unchangeable, options.except(:immutable_nil).merge!(value: value))
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
@@ -1,3 +1,3 @@
1
1
  module CanHasValidations
2
- VERSION = '1.1.0'
2
+ VERSION = '1.3.2'
3
3
  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.1.0
4
+ version: 1.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - thomas morgan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-05 00:00:00.000000000 Z
11
+ date: 2021-05-24 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.1'
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.1'
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,10 +57,13 @@ 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
64
68
  - lib/can_has_validations/validators/ipaddr_validator.rb
65
69
  - lib/can_has_validations/validators/ordering_validator.rb
@@ -99,7 +103,8 @@ files:
99
103
  - test/dummy/script/rails
100
104
  - test/test_helper.rb
101
105
  homepage: https://github.com/zarqman/can_has_validations
102
- licenses: []
106
+ licenses:
107
+ - MIT
103
108
  metadata: {}
104
109
  post_install_message:
105
110
  rdoc_options: []
@@ -116,39 +121,39 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
121
  - !ruby/object:Gem::Version
117
122
  version: '0'
118
123
  requirements: []
119
- rubygems_version: 3.0.8
124
+ rubygems_version: 3.2.15
120
125
  signing_key:
121
126
  specification_version: 4
122
127
  summary: Assorted Rails 5.x-6.x validators
123
128
  test_files:
124
- - test/dummy/app/controllers/application_controller.rb
125
- - test/dummy/app/views/layouts/application.html.erb
129
+ - test/can_has_validations_test.rb
130
+ - test/dummy/README.rdoc
131
+ - test/dummy/Rakefile
126
132
  - test/dummy/app/assets/javascripts/application.js
127
133
  - test/dummy/app/assets/stylesheets/application.css
134
+ - test/dummy/app/controllers/application_controller.rb
128
135
  - test/dummy/app/helpers/application_helper.rb
129
- - test/dummy/config/routes.rb
130
- - test/dummy/config/locales/en.yml
131
- - test/dummy/config/environments/production.rb
132
- - test/dummy/config/environments/development.rb
133
- - test/dummy/config/environments/test.rb
134
- - test/dummy/config/environment.rb
136
+ - test/dummy/app/views/layouts/application.html.erb
135
137
  - test/dummy/config/application.rb
136
- - test/dummy/config/database.yml
137
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
138
144
  - test/dummy/config/initializers/backtrace_silencers.rb
145
+ - test/dummy/config/initializers/inflections.rb
139
146
  - test/dummy/config/initializers/mime_types.rb
147
+ - test/dummy/config/initializers/secret_token.rb
140
148
  - test/dummy/config/initializers/session_store.rb
141
149
  - test/dummy/config/initializers/wrap_parameters.rb
142
- - test/dummy/config/initializers/secret_token.rb
143
- - test/dummy/config/initializers/inflections.rb
150
+ - test/dummy/config/locales/en.yml
151
+ - test/dummy/config/routes.rb
144
152
  - test/dummy/config.ru
145
- - test/dummy/script/rails
146
- - test/dummy/Rakefile
147
- - test/dummy/public/favicon.ico
153
+ - test/dummy/log/test.log
154
+ - test/dummy/public/404.html
148
155
  - test/dummy/public/422.html
149
156
  - test/dummy/public/500.html
150
- - test/dummy/public/404.html
151
- - test/dummy/log/test.log
152
- - test/dummy/README.rdoc
153
- - test/can_has_validations_test.rb
157
+ - test/dummy/public/favicon.ico
158
+ - test/dummy/script/rails
154
159
  - test/test_helper.rb