can_has_validations 0.6.2 → 1.1.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: 8be9db4769da71714aaf241907b6f4a25c08ba8e107e9c707aa28f9085fa8404
4
- data.tar.gz: 7839b571a0e5a397b6fd645bba0b28b3af5b0ea7c5fdbccca0d21e9dc1c7a9ac
3
+ metadata.gz: 4b8c22fcbf2964d57091e6b566e5d05a1b501f286722ae26e4c63be5d42989f9
4
+ data.tar.gz: c2cdd19af9fa8459007d506f93e4dec5b1311c173d4fdf087c7438a1d387d97f
5
5
  SHA512:
6
- metadata.gz: c06cad10d8a65d2c943ff3dd55c7e8d3d7a8c5a6461f0a7c153875b14c60733d539c01b4424b5fa7b204066d1956bf56e270ae24595dba5b10e0daed139a548f
7
- data.tar.gz: b9d5dd930c534575fe75a70589b18111f7c47d22978ceb53e1eab2f600908c8ebc2623e1b7a359f12a8873ca7ef6c014921279c9230122e4837b3160a85073c6
6
+ metadata.gz: 115570baed1e6a8061612de3f6428599ab4b76afab032756db8a7df5d5aa8bf8ec76f91d52849b5a28b154349965028c5f295350e4fd8f62d86b7f298e046cf2
7
+ data.tar.gz: fa601d54efefa5ee3d83299b9838ba6684450009ef3f782e3904de6405ea6ecd05869e79e89daadf42904f8cfe8c791a2f397a1fb24ec08cd9ff1ced1bd269a9
@@ -1,4 +1,4 @@
1
- Copyright 2012 YOURNAME
1
+ Copyright 2012-2020 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
@@ -10,6 +10,7 @@ Validations provided:
10
10
  * Existence
11
11
  * Grandparent
12
12
  * Hostname
13
+ * IP address
13
14
  * Ordering
14
15
  * URL
15
16
  * Write Once
@@ -147,14 +148,48 @@ TLD, so as to not fail as ICANN continues to add TLDs.
147
148
  # allows '_abc.example.com'
148
149
  validates :domain, hostname: {allow_underscore: true}
149
150
 
151
+ # allows '4.0/25.3.2.1.example.com'
152
+ validates :domain, hostname: {allow_slash: true}
153
+
150
154
  # allows 'a.example.com', but not 'example.com'
151
155
  validates :domain, hostname: {segments: 3..100}
152
156
 
157
+ # allows 'subdomain'
158
+ validates :subdomain, hostname: {segments: 1, skip_tld: true}
159
+
153
160
  # allows '1.2.3.4' or 'a.example.com'
154
161
  validates :domain, hostname: {allow_ip: true}
155
162
  # use 4 or 6 for ipv4 or ipv6 only
156
163
 
157
164
 
165
+ ## IP address validator ##
166
+
167
+ Ensures an attribute is generally formatted as a IP or IP block.
168
+
169
+ # allows '1.2.3.4' or '::1'
170
+ validates :ip, ipaddr: true
171
+
172
+ # allows '1.2.3.0/24' or '2001:db8::/64'
173
+ validates :cidr, ipaddr: {allow_block: true}
174
+
175
+ # if an ip block, the attribute must be fully contained within an allowed block.
176
+ # allows '10.0.0.1' and '10.0.0.0/24', but not '10.0.0.0/15'
177
+ validates :private_ip, ipaddr: {
178
+ allow_block: true,
179
+ within: [IPAddr.new('10.0.0.0/16'), '127.0.0.1']
180
+ # allowed IPs and blocks may be IPAddrs or Strings
181
+ }
182
+
183
+ # the inverse of :within
184
+ validates :public_ip6, ipaddr: {without: ['fc00::/7']]}
185
+
186
+ # :within and :without may also be procs or method names
187
+ validates :ip, ipaddr: {
188
+ within: :some_method,
189
+ without: ->(record){ ... }
190
+ }
191
+
192
+
158
193
  ## Ordering validators ##
159
194
 
160
195
  Ensures two attribute values maintain a relative order to one another. This is
@@ -180,13 +215,17 @@ Always skips over nil values; use `:presence` to validate those.
180
215
 
181
216
  ## URL validator ##
182
217
 
183
- Ensure an attribute is generally formatted as a URL. If `addressable/uri` is
218
+ Ensures an attribute is generally formatted as a URL. If `addressable/uri` is
184
219
  already loaded, it will be used to parse IDN's. Additionally, allowed schemes
185
220
  can be specified; they default to ['http','https'].
186
221
 
187
222
  validates :website, url: true
188
223
  validates :secure_url, url: {scheme: 'https'}
189
224
 
225
+ # Dynamic list of schemes. *Must* return an array.
226
+ validates :git, url: {scheme: :some_method}
227
+ validates :old_school, url: {scheme: ->(record){ %w(ftp gopher) }}
228
+
190
229
  # With IDN parsing:
191
230
  require 'addressable/uri'
192
231
  validates :website, url: true
@@ -198,8 +237,8 @@ can be specified; they default to ['http','https'].
198
237
 
199
238
  ## Write Once validator ##
200
239
 
201
- Ensure that once a value is written, it becomes readonly. There are two uses
202
- for this.
240
+ Ensures that once a value is written, it becomes readonly. There are a few
241
+ uses for this.
203
242
 
204
243
  The first is as an equivalent to `attr_readonly :user_id` except that it also
205
244
  produces a validation error instead of silently ignoring the change as
@@ -214,6 +253,10 @@ sorts.
214
253
 
215
254
  validates :user_id, allow_nil: true, write_once: true
216
255
 
256
+ The third use is to allow a nil value, and treat the nil also as write-once.
257
+
258
+ validates :source, write_once: {immutable_nil: true}
259
+
217
260
 
218
261
  ## Error messages
219
262
 
@@ -225,6 +268,9 @@ Default messages are as follows:
225
268
  messages:
226
269
  invalid_email: "is an invalid email"
227
270
  invalid_hostname: "is an invalid hostname"
271
+ invalid_ip: "is an invalid IP"
272
+ ip_not_allowed: "is not an allowed IP"
273
+ single_ip_required: "must be a single IP"
228
274
  invalid_url: "is an invalid URL"
229
275
  unchangeable: "cannot be changed"
230
276
  before: "must be before %{attribute2}"
@@ -233,4 +279,4 @@ Default messages are as follows:
233
279
 
234
280
  ## Compatibility ##
235
281
 
236
- Tested with Ruby 2.3-2.5 and ActiveSupport and ActiveModel 4.2-5.2.
282
+ The current version is tested with Ruby 2.5-2.6 and ActiveModel 5.2-6.0.
@@ -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 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
-
@@ -1,15 +1,55 @@
1
1
  # Ensure an attribute is generally formatted as an email.
2
2
  # eg: validates :user_email, email: true
3
3
 
4
+ require_relative 'hostname_validator'
5
+
4
6
  module ActiveModel::Validations
5
7
  class EmailValidator < ActiveModel::EachValidator
6
8
 
7
- EMAIL_REGEXP = /\A([a-z0-9._+-]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
9
+ EMAIL_REGEXP = /\A([a-z0-9._+-]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
10
+ SEGMENT_REGEXP = /\A[a-z0-9+_-]+\z/i
11
+ LABEL_REGEXP = HostnameValidator::LABEL_REGEXP
12
+ FINAL_LABEL_REGEXP = HostnameValidator::FINAL_LABEL_REGEXP
8
13
 
9
14
  def validate_each(record, attribute, value)
10
- unless value =~ EMAIL_REGEXP
15
+ unless email_valid?(value)
11
16
  record.errors.add(attribute, :invalid_email, options.merge(value: value))
12
17
  end
13
18
  end
19
+
20
+ def email_valid?(value)
21
+ return unless value
22
+ recipient, domain = value.split('@', 2)
23
+ is_valid = true
24
+
25
+ recipient ||= ''
26
+ is_valid &&= recipient.length <= 255
27
+ is_valid &&= recipient !~ /\.\./
28
+ is_valid &&= !recipient.starts_with?('.')
29
+ is_valid &&= !recipient.ends_with?('.')
30
+ recipient.split('.').each do |segment|
31
+ is_valid &&= segment =~ SEGMENT_REGEXP
32
+ end
33
+
34
+ domain ||= ''
35
+ if defined?(Addressable::IDNA)
36
+ domain &&= Addressable::IDNA.to_ascii(domain)
37
+ end
38
+ labels = domain.split('.')
39
+ is_valid &&= domain.length <= 255
40
+ is_valid &&= domain !~ /\.\./
41
+ is_valid &&= labels.size.in? 2..100
42
+ labels.each_with_index do |label, idx|
43
+ is_valid &&= label.length <= 63
44
+ if idx+1==labels.size
45
+ is_valid &&= label =~ FINAL_LABEL_REGEXP
46
+ else
47
+ is_valid &&= label =~ LABEL_REGEXP
48
+ end
49
+ end
50
+
51
+ is_valid
52
+ end
53
+
14
54
  end
15
55
  end
@@ -16,7 +16,8 @@ module ActiveModel::Validations
16
16
  if cousin.nil?
17
17
  options[:allow_nil]
18
18
  else
19
- association.send(options[:parent]) == cousin.send(options[:parent])
19
+ association &&
20
+ association.send(options[:parent]) == cousin.send(options[:parent])
20
21
  end
21
22
  end
22
23
  unless all_match
@@ -18,19 +18,23 @@
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
24
26
  # allows '1.2.3.4' or 'a.example.com'
27
+ # validates :subdomain, hostname: {skip_tld: true, segments: 1}
28
+ # allows 'subdomain1'
25
29
 
26
30
  require 'resolv'
27
31
 
28
32
  module ActiveModel::Validations
29
33
  class HostnameValidator < ActiveModel::EachValidator
30
34
 
31
- LABEL_REGEXP = /\A([a-zA-Z0-9_]([a-zA-Z0-9_-]+)?)?[a-zA-Z0-9]\z/
32
- FINAL_LABEL_REGEXP = /\A(xn--[a-zA-Z0-9]{2,}|[a-zA-Z]{2,})\z/
33
- 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)
34
38
 
35
39
  def validate_each(record, attribute, value)
36
40
  case options[:allow_ip]
@@ -53,10 +57,11 @@ module ActiveModel::Validations
53
57
  is_valid &&= value.length <= 255
54
58
  is_valid &&= value !~ /\.\./
55
59
  is_valid &&= value !~ /_/ unless options[:allow_underscore]
60
+ is_valid &&= value !~ %r{/} unless options[:allow_slash]
56
61
  is_valid &&= labels.size.in? segments
57
62
  labels.each_with_index do |label, idx|
58
63
  is_valid &&= label.length <= 63
59
- if idx+1==labels.size
64
+ if !options[:skip_tld] && idx+1==labels.size
60
65
  is_valid &&= label =~ FINAL_LABEL_REGEXP
61
66
  elsif options[:allow_wildcard] && idx==0
62
67
  is_valid &&= label=='*' || label =~ LABEL_REGEXP
@@ -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
@@ -7,7 +7,13 @@
7
7
  module ActiveModel::Validations
8
8
  class UrlValidator < ActiveModel::EachValidator
9
9
  def validate_each(record, attribute, value)
10
- allowed_schemes = Array.wrap(options[:scheme] || %w(http https))
10
+ allowed_schemes = if options[:scheme].respond_to?(:call)
11
+ options[:scheme].call(record)
12
+ elsif options[:scheme].is_a?(Symbol)
13
+ record.send(options[:scheme])
14
+ else
15
+ Array.wrap(options[:scheme] || %w(http https))
16
+ end
11
17
 
12
18
  if defined?(Addressable::URI)
13
19
  u = Addressable::URI.parse(value) rescue nil
@@ -16,7 +22,7 @@ module ActiveModel::Validations
16
22
  u2 = u = URI.parse(value) rescue nil
17
23
  end
18
24
  if !u || !u2 || u.relative? || allowed_schemes.exclude?(u.scheme)
19
- record.errors.add(attribute, :invalid_url, options.merge(value: value))
25
+ record.errors.add(attribute, :invalid_url, options.merge(value: value, scheme: allowed_schemes))
20
26
  end
21
27
  end
22
28
  end
@@ -1,3 +1,3 @@
1
1
  module CanHasValidations
2
- VERSION = '0.6.2'
2
+ VERSION = '1.1.0'
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: 0.6.2
4
+ version: 1.1.0
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: 2018-08-01 00:00:00.000000000 Z
11
+ date: 2020-12-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -16,20 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '3.0'
19
+ version: '5.0'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '6'
22
+ version: '6.1'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: '3.0'
29
+ version: '5.0'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '6'
32
+ version: '6.1'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: sqlite3
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -44,7 +44,7 @@ dependencies:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
46
  version: '0'
47
- description: Assorted Rails 4.x-5.x validators.
47
+ description: Assorted Rails 5.x-6.x validators.
48
48
  email:
49
49
  - tm@iprog.com
50
50
  executables: []
@@ -61,6 +61,7 @@ files:
61
61
  - lib/can_has_validations/validators/existence_validator.rb
62
62
  - lib/can_has_validations/validators/grandparent_validator.rb
63
63
  - lib/can_has_validations/validators/hostname_validator.rb
64
+ - lib/can_has_validations/validators/ipaddr_validator.rb
64
65
  - lib/can_has_validations/validators/ordering_validator.rb
65
66
  - lib/can_has_validations/validators/url_validator.rb
66
67
  - lib/can_has_validations/validators/write_once_validator.rb
@@ -100,7 +101,7 @@ files:
100
101
  homepage: https://github.com/zarqman/can_has_validations
101
102
  licenses: []
102
103
  metadata: {}
103
- post_install_message:
104
+ post_install_message:
104
105
  rdoc_options: []
105
106
  require_paths:
106
107
  - lib
@@ -115,40 +116,39 @@ required_rubygems_version: !ruby/object:Gem::Requirement
115
116
  - !ruby/object:Gem::Version
116
117
  version: '0'
117
118
  requirements: []
118
- rubyforge_project:
119
- rubygems_version: 2.7.6
120
- signing_key:
119
+ rubygems_version: 3.0.8
120
+ signing_key:
121
121
  specification_version: 4
122
- summary: Assorted Rails 4.x-5.x validators
122
+ summary: Assorted Rails 5.x-6.x validators
123
123
  test_files:
124
- - test/can_has_validations_test.rb
124
+ - test/dummy/app/controllers/application_controller.rb
125
+ - test/dummy/app/views/layouts/application.html.erb
125
126
  - test/dummy/app/assets/javascripts/application.js
126
127
  - test/dummy/app/assets/stylesheets/application.css
127
- - test/dummy/app/controllers/application_controller.rb
128
128
  - test/dummy/app/helpers/application_helper.rb
129
- - test/dummy/app/views/layouts/application.html.erb
130
- - test/dummy/config/application.rb
131
- - test/dummy/config/boot.rb
132
- - test/dummy/config/database.yml
133
- - test/dummy/config/environment.rb
134
- - test/dummy/config/environments/development.rb
129
+ - test/dummy/config/routes.rb
130
+ - test/dummy/config/locales/en.yml
135
131
  - test/dummy/config/environments/production.rb
132
+ - test/dummy/config/environments/development.rb
136
133
  - test/dummy/config/environments/test.rb
134
+ - test/dummy/config/environment.rb
135
+ - test/dummy/config/application.rb
136
+ - test/dummy/config/database.yml
137
+ - test/dummy/config/boot.rb
137
138
  - test/dummy/config/initializers/backtrace_silencers.rb
138
- - test/dummy/config/initializers/inflections.rb
139
139
  - test/dummy/config/initializers/mime_types.rb
140
- - test/dummy/config/initializers/secret_token.rb
141
140
  - test/dummy/config/initializers/session_store.rb
142
141
  - test/dummy/config/initializers/wrap_parameters.rb
143
- - test/dummy/config/locales/en.yml
144
- - test/dummy/config/routes.rb
142
+ - test/dummy/config/initializers/secret_token.rb
143
+ - test/dummy/config/initializers/inflections.rb
145
144
  - test/dummy/config.ru
146
- - test/dummy/log/test.log
147
- - test/dummy/public/404.html
145
+ - test/dummy/script/rails
146
+ - test/dummy/Rakefile
147
+ - test/dummy/public/favicon.ico
148
148
  - test/dummy/public/422.html
149
149
  - test/dummy/public/500.html
150
- - test/dummy/public/favicon.ico
151
- - test/dummy/Rakefile
150
+ - test/dummy/public/404.html
151
+ - test/dummy/log/test.log
152
152
  - test/dummy/README.rdoc
153
- - test/dummy/script/rails
153
+ - test/can_has_validations_test.rb
154
154
  - test/test_helper.rb