can_has_validations 0.6.3 → 1.2.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: d565ec38823188e137ba900db6247e643f3c436a6a8f0be7d210a8af0f2606e6
4
- data.tar.gz: 47fc9fc83d7224d9831d66a98e967b161b6f3fa66de7186b8b51c85ffc8287d1
3
+ metadata.gz: 92f14adff8817a7d4d4d6d5b9a1b00a0860f9279ca86dafaad567e67e4970ee6
4
+ data.tar.gz: ea792545cf1ed844ec5ad498068dd9990712b240c31f3e214d6c3518f429f9b4
5
5
  SHA512:
6
- metadata.gz: 1cfa321ceee3431a7b2a99027794a655a5cd2b0a570eb715ccbaf06be183956689bfb7e369b0927d31f6a80f781591fae5422803df836fe2b41a9e52babe3bbd
7
- data.tar.gz: 34d93b15d141061ded5dc58c2cdb62f2abe646e60c4e49e6b4ee8856d76356a3e83be0c080db3f71a5499ae095955299a3cfc978ee90d86fc50c4b465e289662
6
+ metadata.gz: c2b1cb68de2eeb27e3d1a7b0c8b86d9d64f76f62a7f51feddfc5e62c9f009f14caadf7fd6eafac768c47e77fb31ed27cc8edab196b5d565d626582e0025403db
7
+ data.tar.gz: a7a1fa2e08fe737f88b9e428047d01b398a852a82528439483abfb1251027d0e5542429b82c981f8dc3cf5ece39aca0e3404e009bc5d8b75a15850bf33585dd7
@@ -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,6 +148,9 @@ 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
 
@@ -158,6 +162,34 @@ TLD, so as to not fail as ICANN continues to add TLDs.
158
162
  # use 4 or 6 for ipv4 or ipv6 only
159
163
 
160
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
+
161
193
  ## Ordering validators ##
162
194
 
163
195
  Ensures two attribute values maintain a relative order to one another. This is
@@ -183,13 +215,17 @@ Always skips over nil values; use `:presence` to validate those.
183
215
 
184
216
  ## URL validator ##
185
217
 
186
- 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
187
219
  already loaded, it will be used to parse IDN's. Additionally, allowed schemes
188
220
  can be specified; they default to ['http','https'].
189
221
 
190
222
  validates :website, url: true
191
223
  validates :secure_url, url: {scheme: 'https'}
192
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
+
193
229
  # With IDN parsing:
194
230
  require 'addressable/uri'
195
231
  validates :website, url: true
@@ -201,8 +237,8 @@ can be specified; they default to ['http','https'].
201
237
 
202
238
  ## Write Once validator ##
203
239
 
204
- Ensure that once a value is written, it becomes readonly. There are two uses
205
- for this.
240
+ Ensures that once a value is written, it becomes readonly. There are a few
241
+ uses for this.
206
242
 
207
243
  The first is as an equivalent to `attr_readonly :user_id` except that it also
208
244
  produces a validation error instead of silently ignoring the change as
@@ -217,6 +253,10 @@ sorts.
217
253
 
218
254
  validates :user_id, allow_nil: true, write_once: true
219
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
+
220
260
 
221
261
  ## Error messages
222
262
 
@@ -228,6 +268,9 @@ Default messages are as follows:
228
268
  messages:
229
269
  invalid_email: "is an invalid email"
230
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"
231
274
  invalid_url: "is an invalid URL"
232
275
  unchangeable: "cannot be changed"
233
276
  before: "must be before %{attribute2}"
@@ -236,4 +279,4 @@ Default messages are as follows:
236
279
 
237
280
  ## Compatibility ##
238
281
 
239
- 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,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 = /\A([a-zA-Z0-9_]([a-zA-Z0-9_-]+)?)?[a-zA-Z0-9]\z/
34
- FINAL_LABEL_REGEXP = /\A(xn--[a-zA-Z0-9]{2,}|[a-zA-Z]{2,})\z/
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
@@ -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
@@ -12,14 +12,20 @@ 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?
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)
23
29
  record.errors.add(attribute, :unchangeable, options.except(:immutable_nil).merge!(value: value))
24
30
  end
25
31
  end
@@ -1,3 +1,3 @@
1
1
  module CanHasValidations
2
- VERSION = '0.6.3'
2
+ VERSION = '1.2.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.3
4
+ version: 1.2.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-07 00:00:00.000000000 Z
11
+ date: 2020-12-29 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.2'
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.2'
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
@@ -98,9 +99,10 @@ files:
98
99
  - test/dummy/script/rails
99
100
  - test/test_helper.rb
100
101
  homepage: https://github.com/zarqman/can_has_validations
101
- licenses: []
102
+ licenses:
103
+ - MIT
102
104
  metadata: {}
103
- post_install_message:
105
+ post_install_message:
104
106
  rdoc_options: []
105
107
  require_paths:
106
108
  - lib
@@ -115,40 +117,39 @@ required_rubygems_version: !ruby/object:Gem::Requirement
115
117
  - !ruby/object:Gem::Version
116
118
  version: '0'
117
119
  requirements: []
118
- rubyforge_project:
119
- rubygems_version: 2.7.6
120
- signing_key:
120
+ rubygems_version: 3.0.9
121
+ signing_key:
121
122
  specification_version: 4
122
- summary: Assorted Rails 4.x-5.x validators
123
+ summary: Assorted Rails 5.x-6.x validators
123
124
  test_files:
124
- - test/can_has_validations_test.rb
125
+ - test/dummy/app/controllers/application_controller.rb
126
+ - test/dummy/app/views/layouts/application.html.erb
125
127
  - test/dummy/app/assets/javascripts/application.js
126
128
  - test/dummy/app/assets/stylesheets/application.css
127
- - test/dummy/app/controllers/application_controller.rb
128
129
  - 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
130
+ - test/dummy/config/routes.rb
131
+ - test/dummy/config/locales/en.yml
135
132
  - test/dummy/config/environments/production.rb
133
+ - test/dummy/config/environments/development.rb
136
134
  - test/dummy/config/environments/test.rb
135
+ - test/dummy/config/environment.rb
136
+ - test/dummy/config/application.rb
137
+ - test/dummy/config/database.yml
138
+ - test/dummy/config/boot.rb
137
139
  - test/dummy/config/initializers/backtrace_silencers.rb
138
- - test/dummy/config/initializers/inflections.rb
139
140
  - test/dummy/config/initializers/mime_types.rb
140
- - test/dummy/config/initializers/secret_token.rb
141
141
  - test/dummy/config/initializers/session_store.rb
142
142
  - test/dummy/config/initializers/wrap_parameters.rb
143
- - test/dummy/config/locales/en.yml
144
- - test/dummy/config/routes.rb
143
+ - test/dummy/config/initializers/secret_token.rb
144
+ - test/dummy/config/initializers/inflections.rb
145
145
  - test/dummy/config.ru
146
- - test/dummy/log/test.log
147
- - test/dummy/public/404.html
146
+ - test/dummy/script/rails
147
+ - test/dummy/Rakefile
148
+ - test/dummy/public/favicon.ico
148
149
  - test/dummy/public/422.html
149
150
  - test/dummy/public/500.html
150
- - test/dummy/public/favicon.ico
151
- - test/dummy/Rakefile
151
+ - test/dummy/public/404.html
152
+ - test/dummy/log/test.log
152
153
  - test/dummy/README.rdoc
153
- - test/dummy/script/rails
154
+ - test/can_has_validations_test.rb
154
155
  - test/test_helper.rb