validation_kit 2.0.0 → 2.0.1
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 +4 -4
- data/CHANGELOG.md +70 -0
- data/README.md +55 -28
- data/lib/validation_kit/country_option.rb +35 -0
- data/lib/validation_kit/email_validator/email_validator.rb +23 -2
- data/lib/validation_kit/mixed_case_validator/config/locales/en.yml +2 -2
- data/lib/validation_kit/mixed_case_validator/config/locales/fr.yml +2 -2
- data/lib/validation_kit/mixed_case_validator/mixed_case_validator.rb +3 -0
- data/lib/validation_kit/phone_validator/phone_validator.rb +59 -30
- data/lib/validation_kit/postal_code_validator/postal_code_validator.rb +3 -10
- data/lib/validation_kit/version.rb +1 -1
- data/lib/validation_kit.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 83f8486330f7a2179f4655e0cd938355d9b79c9b96279238829b4c3c7a860151
|
|
4
|
+
data.tar.gz: 479ec62978042d99be1efb5985c3efad7e18615f5450374cd131ebd2a907ff06
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b2940c397a76ebedbcbcb9802081159eb58b837c7284a6f77e5487081164b23447a30092485b7e765c295af88e8fdb6ba6fd5aac355cf46a1795b7beaad451fd
|
|
7
|
+
data.tar.gz: 5d59577b6a5d73bb96f1063b1d8b8125856d23d4d48d01c7e67584a47067e3df43054db60aa768c4c1b2bf175eea8fff8198000f8d1f0262b2175b3470c59d25
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,75 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.0.1 (2026-10-02)
|
|
4
|
+
|
|
5
|
+
### Breaking changes
|
|
6
|
+
|
|
7
|
+
- The email length limits (254 characters in all, 64 before the `@`) now count
|
|
8
|
+
every character, as RFC 5321 does, including the quotes around a quoted local
|
|
9
|
+
part and each backslash escape. So a quoted local part of 63 characters plus
|
|
10
|
+
its quotes, which used to pass, is now too long. The limits are checked by
|
|
11
|
+
the validator and the new `ValidationKit::EmailValidator.valid_address?`, not
|
|
12
|
+
by `EMAIL_ADDRESS_RE`: code that uses the regex directly no longer gets them,
|
|
13
|
+
and should call `valid_address?` instead.
|
|
14
|
+
- An `area_key:` that isn't an Australian state or territory (NSW, ACT, VIC,
|
|
15
|
+
TAS, QLD, SA, NT, WA, in any case) now raises `ArgumentError` when the model
|
|
16
|
+
is defined, and `format_as_phone` raises for one too. It used to silently add
|
|
17
|
+
the NSW/ACT area code (02), so `"Victoria"` or a typo stored a wrong number.
|
|
18
|
+
Leaving `area_key:` out still means NSW/ACT.
|
|
19
|
+
- A symbol `country:` option that's neither a method on the model nor a
|
|
20
|
+
two-letter country code (such as a misspelled method name,
|
|
21
|
+
`country: :billing_contry`) now raises `ArgumentError` when validating. It
|
|
22
|
+
used to fall back to the model's `country` method, or skip validation
|
|
23
|
+
silently.
|
|
24
|
+
- The bundled mixed case messages no longer start with the attribute's name, so
|
|
25
|
+
`full_messages` names it once ("First name cannot be in all caps", not "First
|
|
26
|
+
name First name cannot be in all caps"), like Rails' own messages:
|
|
27
|
+
- `errors[:first_name]` is now `["cannot be in all caps"]`. Code that shows
|
|
28
|
+
`errors[attribute]` on its own should use `full_messages_for` instead.
|
|
29
|
+
- `attribute_name:` no longer changes the default message. `full_messages`
|
|
30
|
+
uses the attribute's translated name (`human_attribute_name`) instead; to
|
|
31
|
+
rename it, translate the attribute. `attribute_name:` still fills in
|
|
32
|
+
`%{item}` in custom messages and translations, which keep working.
|
|
33
|
+
- Apps that set `errors.format` to `"%{message}"` to hide the doubled name now
|
|
34
|
+
get messages with no name at all, and can drop that setting.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- US and Canadian phone numbers with extra digits right after the number and no
|
|
39
|
+
extension marker in front of them (any letter, as in `x`, `ext` or
|
|
40
|
+
`extension`, or `#`, `#` or `№`) are now invalid, with or without `set:`. A
|
|
41
|
+
mistyped number such as `212-555-12345` used to pass, and `set:` stored it as
|
|
42
|
+
`(212) 555-1234 5`. This includes non-ASCII digits such as `5`.
|
|
43
|
+
- Without `set:`, a US or Canadian number with letters among its first ten
|
|
44
|
+
digits (`519 444 000 ext 123`) is now invalid, as it already was with `set:`.
|
|
45
|
+
- With `set:`, a stored extension no longer keeps a leading `,` or `;` or
|
|
46
|
+
surrounding spaces (including non-breaking ones): `212-555-1234, ext 5`
|
|
47
|
+
becomes `(212) 555-1234 ext 5`. An extension written with non-ASCII digits
|
|
48
|
+
(`x5`) is kept instead of being dropped. Other marked extensions (`x5`,
|
|
49
|
+
`ext. 99`, `(x5)`, `#5`, …) are stored as before.
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- The email validator still backtracked catastrophically on input with quote
|
|
53
|
+
characters, despite the 2.0.0 fix: `'a"' * 28 + "@"` (57 characters) took
|
|
54
|
+
about a minute to reject. The two length checks at the start of the regex
|
|
55
|
+
caused it; they're now plain length checks, and that input fails in well
|
|
56
|
+
under a millisecond.
|
|
57
|
+
- Email addresses with non-ASCII letters that case-fold to ASCII ones, such as
|
|
58
|
+
`a@ſx.com`, are rejected, like every other non-ASCII address.
|
|
59
|
+
- `country: :CA` (or `:ca`) now validates as Canada, as the README says. A
|
|
60
|
+
symbol was only ever treated as a method name, so without a `CA` method the
|
|
61
|
+
validator used the model's `country` method instead, or skipped validation
|
|
62
|
+
silently.
|
|
63
|
+
- The phone validator's `area_key:` (and `format_as_phone`'s area key) now
|
|
64
|
+
accepts Australian states in any case, as a string or symbol. `:vic` or
|
|
65
|
+
`"vic"` used to silently get the NSW/ACT area code (02).
|
|
66
|
+
|
|
67
|
+
### Documentation
|
|
68
|
+
|
|
69
|
+
- The README's error-type example used `errors.added?(:first_name, :all_caps)`,
|
|
70
|
+
which is always false for mixed case errors because they carry an `item:`
|
|
71
|
+
option. It now uses `of_kind?`, and explains when `added?` needs `item:`.
|
|
72
|
+
|
|
3
73
|
## 2.0.0 (2026-10-01)
|
|
4
74
|
|
|
5
75
|
### Breaking changes
|
data/README.md
CHANGED
|
@@ -29,6 +29,10 @@ Person.new(email: "Bob").valid? # => false
|
|
|
29
29
|
Person.new(email: "joe@foobar.com").valid? # => true
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
Addresses are limited to 254 characters, and 64 before the `@` (RFC 5321). To
|
|
33
|
+
check an address outside a model, use
|
|
34
|
+
`ValidationKit::EmailValidator.valid_address?("joe@foobar.com")`.
|
|
35
|
+
|
|
32
36
|
## Mixed case
|
|
33
37
|
|
|
34
38
|
Validates that a string isn't all capitals or all lowercase. Values with fewer
|
|
@@ -45,11 +49,11 @@ Person.new(first_name: "bob").valid? # => false, "First name cannot be in all lo
|
|
|
45
49
|
Person.new(first_name: "Bob").valid? # => true
|
|
46
50
|
```
|
|
47
51
|
|
|
48
|
-
| Option | Description
|
|
49
|
-
| ----------------- |
|
|
50
|
-
| `attribute_name:` | Name
|
|
51
|
-
| `all_caps:` | Custom message for all-caps values.
|
|
52
|
-
| `all_lowercase:` | Custom message for all-lowercase values.
|
|
52
|
+
| Option | Description |
|
|
53
|
+
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
54
|
+
| `attribute_name:` | Name for `%{item}` in a custom message, used when the attribute has no translation (a translation wins). It doesn’t change `full_messages`, which use the attribute’s translated name. |
|
|
55
|
+
| `all_caps:` | Custom message for all-caps values. |
|
|
56
|
+
| `all_lowercase:` | Custom message for all-lowercase values. |
|
|
53
57
|
|
|
54
58
|
## Phone
|
|
55
59
|
|
|
@@ -62,11 +66,16 @@ class Person < ActiveRecord::Base
|
|
|
62
66
|
end
|
|
63
67
|
```
|
|
64
68
|
|
|
65
|
-
| Option | Description
|
|
66
|
-
| ----------- |
|
|
67
|
-
| `country:` | See [Specifying the country](#specifying-the-country).
|
|
68
|
-
| `set:` | Reformat the stored value: `"1-212-555-1234 x5"` becomes `"(212) 555-1234 x5"`, `"0298765432"` becomes `"(02) 9876 5432"`.
|
|
69
|
-
| `area_key:` | For Australian landlines entered without an area code, the state whose area code to add
|
|
69
|
+
| Option | Description |
|
|
70
|
+
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
71
|
+
| `country:` | See [Specifying the country](#specifying-the-country). |
|
|
72
|
+
| `set:` | Reformat the stored value: `"1-212-555-1234 x5"` becomes `"(212) 555-1234 x5"`, `"0298765432"` becomes `"(02) 9876 5432"`. |
|
|
73
|
+
| `area_key:` | For Australian landlines entered without an area code, the state whose area code to add: `NSW`, `ACT`, `VIC`, `TAS`, `QLD`, `SA`, `NT` or `WA`, in any case, as a string or symbol (`"VIC"`, `:qld`). It's always a fixed state, not a method name. Leave it out (or blank) for NSW/ACT (`02`); any other value raises `ArgumentError`. |
|
|
74
|
+
|
|
75
|
+
A US or Canadian number may end with an extension, which needs a marker in
|
|
76
|
+
front of its digits: any letter (as in `x`, `ext` or `extension`), or `#`, `#`
|
|
77
|
+
or `№` (`"212-555-1234 ext 5"`). Digits right after the number with no marker,
|
|
78
|
+
as in `"212-555-12345"`, make it invalid.
|
|
70
79
|
|
|
71
80
|
## Postal code
|
|
72
81
|
|
|
@@ -92,8 +101,9 @@ case-insensitive (`"CA"`, `"ca"`, or `:CA`), and validation is skipped when the
|
|
|
92
101
|
country isn't supported.
|
|
93
102
|
|
|
94
103
|
```ruby
|
|
95
|
-
# A fixed country code
|
|
104
|
+
# A fixed country code, as a string or a two-letter symbol
|
|
96
105
|
validates :phone, phone: { country: "CA" }
|
|
106
|
+
validates :phone, phone: { country: :ca }
|
|
97
107
|
|
|
98
108
|
# The name of a method on your model that returns the country code
|
|
99
109
|
validates :phone, phone: { country: :billing_country }
|
|
@@ -102,15 +112,29 @@ validates :phone, phone: { country: :billing_country }
|
|
|
102
112
|
validates :phone, phone: true
|
|
103
113
|
```
|
|
104
114
|
|
|
115
|
+
A symbol is a method name if your model has that public method; otherwise a
|
|
116
|
+
two-letter symbol is a country code. Any other symbol (such as a misspelled
|
|
117
|
+
method name) raises `ArgumentError` when validating, rather than skipping
|
|
118
|
+
validation. Two things that check can't catch: a misspelled two-letter method
|
|
119
|
+
name is read as a country code (and skipped if that country isn't supported),
|
|
120
|
+
and with `allow_nil:` or `allow_blank:` it only runs once the attribute has a
|
|
121
|
+
value.
|
|
122
|
+
|
|
105
123
|
## Error messages
|
|
106
124
|
|
|
107
125
|
Errors are added with standard types, so the usual ActiveModel tools work:
|
|
108
126
|
|
|
109
127
|
```ruby
|
|
110
|
-
person.errors.
|
|
111
|
-
person.errors.
|
|
128
|
+
person.errors.of_kind?(:email, :invalid) # email, phone, postal code
|
|
129
|
+
person.errors.of_kind?(:first_name, :all_caps) # mixed case (also :all_lowercase)
|
|
112
130
|
```
|
|
113
131
|
|
|
132
|
+
`of_kind?` checks the attribute and error type. `added?` also compares the
|
|
133
|
+
error's options, and mixed case errors carry an `item:` option (the
|
|
134
|
+
attribute's name, for messages that use `%{item}`; see
|
|
135
|
+
[Translation keys](#translation-keys) for how it's chosen), so for them
|
|
136
|
+
`added?` only matches if you pass the same `item:`.
|
|
137
|
+
|
|
114
138
|
Pass `message:` (or `all_caps:` / `all_lowercase:` for mixed case) for a custom
|
|
115
139
|
message, or translate it like any other Rails validation error:
|
|
116
140
|
|
|
@@ -122,13 +146,14 @@ en:
|
|
|
122
146
|
person:
|
|
123
147
|
attributes:
|
|
124
148
|
email:
|
|
125
|
-
invalid: "
|
|
149
|
+
invalid: "doesn’t look like an email address"
|
|
126
150
|
first_name:
|
|
127
|
-
all_caps: "
|
|
151
|
+
all_caps: "can’t be all capitals"
|
|
128
152
|
```
|
|
129
153
|
|
|
130
|
-
|
|
131
|
-
|
|
154
|
+
Like Rails’ own messages, these leave out the attribute’s name, because
|
|
155
|
+
`full_messages` adds it: "First name can’t be all capitals". Use `activemodel`
|
|
156
|
+
instead of `activerecord` for classes that aren't ActiveRecord models. The gem includes English and French messages for the
|
|
132
157
|
mixed case errors.
|
|
133
158
|
|
|
134
159
|
## Translation keys
|
|
@@ -146,17 +171,19 @@ other ActiveModel classes:
|
|
|
146
171
|
|
|
147
172
|
A `message:` option (or `all_caps:` / `all_lowercase:`) skips the lookup.
|
|
148
173
|
|
|
149
|
-
| Validator | Error type | Default message (en)
|
|
150
|
-
| ------------- | ---------------- |
|
|
151
|
-
| `email` | `:invalid` | "is invalid" (from Rails)
|
|
152
|
-
| `phone` | `:invalid` | "is invalid" (from Rails)
|
|
153
|
-
| `postal_code` | `:invalid` | "is invalid" (from Rails)
|
|
154
|
-
| `mixed_case` | `:all_caps` | "
|
|
155
|
-
| `mixed_case` | `:all_lowercase` | "
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
`
|
|
159
|
-
|
|
174
|
+
| Validator | Error type | Default message (en) | Default message (fr) |
|
|
175
|
+
| ------------- | ---------------- | ---------------------------- | ------------------------------------------ |
|
|
176
|
+
| `email` | `:invalid` | "is invalid" (from Rails) | from Rails' locale files (e.g. rails-i18n) |
|
|
177
|
+
| `phone` | `:invalid` | "is invalid" (from Rails) | from Rails' locale files (e.g. rails-i18n) |
|
|
178
|
+
| `postal_code` | `:invalid` | "is invalid" (from Rails) | from Rails' locale files (e.g. rails-i18n) |
|
|
179
|
+
| `mixed_case` | `:all_caps` | "cannot be in all caps" | "ne peut pas être en majuscules" |
|
|
180
|
+
| `mixed_case` | `:all_lowercase` | "cannot be in all lowercase" | "ne peut pas être en minuscules" |
|
|
181
|
+
|
|
182
|
+
Like Rails' own messages, the mixed case messages leave out the attribute's
|
|
183
|
+
name, which `full_messages` adds: "First name cannot be in all caps". A custom
|
|
184
|
+
message or translation can include it as `%{item}`: the attribute's
|
|
185
|
+
translation at `activerecord.attributes.person.<attribute>`, then the
|
|
186
|
+
`attribute_name:` option, then the humanized attribute name.
|
|
160
187
|
|
|
161
188
|
## Credits
|
|
162
189
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ValidationKit
|
|
4
|
+
# The country: option shared by the phone and postal code validators.
|
|
5
|
+
module CountryOption
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
# The country code to validate against, or nil to skip validation:
|
|
9
|
+
# - a String is the code itself ("CA")
|
|
10
|
+
# - a Symbol is the name of a method on the record that returns the code
|
|
11
|
+
# (:billing_country); if there's no such method, a two-letter symbol is
|
|
12
|
+
# the code itself (:CA), and anything else is a mistake, so it raises
|
|
13
|
+
# rather than silently skipping validation
|
|
14
|
+
# - without the option, the record's country method, if it has one
|
|
15
|
+
def country_for(record)
|
|
16
|
+
option = options[:country]
|
|
17
|
+
|
|
18
|
+
case option
|
|
19
|
+
when String
|
|
20
|
+
option
|
|
21
|
+
when Symbol
|
|
22
|
+
if record.respond_to?(option)
|
|
23
|
+
record.public_send(option)
|
|
24
|
+
elsif option.match?(/\A[a-z]{2}\z/i)
|
|
25
|
+
option
|
|
26
|
+
else
|
|
27
|
+
raise ArgumentError, "country: #{option.inspect} is neither a public method on " \
|
|
28
|
+
"#{record.class.name || record.class.inspect} nor a two-letter country code"
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
record.country if record.respond_to?(:country)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -2,10 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
module ValidationKit
|
|
4
4
|
class EmailValidator < ActiveModel::EachValidator
|
|
5
|
-
|
|
5
|
+
# RFC 5321's length limits: the whole address, and the part before the @.
|
|
6
|
+
# They're checked in validate_each rather than as lookaheads at the start of
|
|
7
|
+
# EMAIL_ADDRESS_RE, where they backtracked exponentially on input like
|
|
8
|
+
# 'a"a"a"…@' (about a minute for 57 characters).
|
|
9
|
+
MAX_LENGTH = 254
|
|
10
|
+
MAX_LOCAL_PART_LENGTH = 64
|
|
11
|
+
|
|
12
|
+
# The address syntax only; validate_each also applies the length limits.
|
|
13
|
+
EMAIL_ADDRESS_RE = /\A(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22))(?:\.(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\.){1,126}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\]))\z/i
|
|
14
|
+
|
|
15
|
+
# Whether address is a valid email address: the syntax and the length
|
|
16
|
+
# limits. For use outside a model; EMAIL_ADDRESS_RE alone skips the limits.
|
|
17
|
+
def self.valid_address?(address)
|
|
18
|
+
address = address.to_s
|
|
19
|
+
# The syntax is ASCII-only, but the regex's /i matching would also let
|
|
20
|
+
# Unicode case-folded letters (ſ, the Kelvin sign) through as s and k.
|
|
21
|
+
return false unless address.ascii_only?
|
|
22
|
+
return false if address.length > MAX_LENGTH
|
|
23
|
+
|
|
24
|
+
# The domain can't contain an @, so the last one ends the local part.
|
|
25
|
+
address.rpartition("@").first.length <= MAX_LOCAL_PART_LENGTH && EMAIL_ADDRESS_RE.match?(address)
|
|
26
|
+
end
|
|
6
27
|
|
|
7
28
|
def validate_each(record, attribute, value)
|
|
8
|
-
return if
|
|
29
|
+
return if self.class.valid_address?(value)
|
|
9
30
|
|
|
10
31
|
record.errors.add(attribute, :invalid, message: options[:message])
|
|
11
32
|
end
|
|
@@ -23,6 +23,9 @@ module ValidationKit
|
|
|
23
23
|
|
|
24
24
|
return if error.nil?
|
|
25
25
|
|
|
26
|
+
# The bundled messages leave the name to full_messages, like Rails' own.
|
|
27
|
+
# item: is still passed for custom messages and translations that use
|
|
28
|
+
# %{item}.
|
|
26
29
|
record.errors.add(attribute, error, item: item_name(record, attribute), message: options[error])
|
|
27
30
|
end
|
|
28
31
|
|
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
module ValidationKit
|
|
4
4
|
class PhoneValidator < ActiveModel::EachValidator
|
|
5
|
+
include CountryOption
|
|
6
|
+
|
|
7
|
+
# Digits right after a US/CA number with no marker in front of them (any
|
|
8
|
+
# letter, as in x, ext or extension, or #, # or №): a mistyped number like
|
|
9
|
+
# 212-555-12345, not an extension. \p{Nd} also catches non-ASCII digits.
|
|
10
|
+
UNMARKED_US_CA_EXTENSION = /\A[^[:alpha:]##№]*\p{Nd}/
|
|
11
|
+
|
|
12
|
+
# Australian states and territories (the area_key: values), and the
|
|
13
|
+
# landline area code each one adds.
|
|
14
|
+
AU_AREA_CODES = {
|
|
15
|
+
"NSW" => "02", "ACT" => "02", "VIC" => "03", "TAS" => "03",
|
|
16
|
+
"QLD" => "07", "SA" => "08", "NT" => "08", "WA" => "08"
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
# area_key: is a fixed state, so a typo fails when the model is loaded
|
|
20
|
+
# rather than silently adding the wrong area code.
|
|
21
|
+
def check_validity!
|
|
22
|
+
area_code_for_key(options[:area_key])
|
|
23
|
+
end
|
|
24
|
+
|
|
5
25
|
def regex_for_country(country_code)
|
|
6
26
|
country_code = country_code.to_s.strip.upcase
|
|
7
27
|
|
|
@@ -15,16 +35,7 @@ module ValidationKit
|
|
|
15
35
|
end
|
|
16
36
|
|
|
17
37
|
def validate_each(record, attribute, value)
|
|
18
|
-
country =
|
|
19
|
-
options[:country]
|
|
20
|
-
elsif options[:country].is_a?(Symbol) && record.respond_to?(options[:country])
|
|
21
|
-
record.public_send(options[:country])
|
|
22
|
-
elsif record.respond_to?(:country)
|
|
23
|
-
record.country
|
|
24
|
-
else
|
|
25
|
-
false
|
|
26
|
-
end
|
|
27
|
-
|
|
38
|
+
country = country_for(record)
|
|
28
39
|
return unless country
|
|
29
40
|
|
|
30
41
|
# accept "us", :US, " US " as well as "US"
|
|
@@ -38,7 +49,12 @@ module ValidationKit
|
|
|
38
49
|
|
|
39
50
|
# allow_blank/allow_nil are handled by ActiveModel on the original value,
|
|
40
51
|
# so input that only becomes blank once cleaned up (e.g. "n/a") is invalid.
|
|
41
|
-
|
|
52
|
+
valid = current_regex.match?(new_value)
|
|
53
|
+
# The US/CA regex only checks the start of the digits; format_as_phone is
|
|
54
|
+
# what rejects extra digits that aren't a marked extension.
|
|
55
|
+
valid &&= !format_as_phone(value, country).nil? if %w[US CA].include?(country)
|
|
56
|
+
|
|
57
|
+
if valid
|
|
42
58
|
if options[:set]
|
|
43
59
|
formatted_phone = format_as_phone(value, country, options[:area_key])
|
|
44
60
|
if formatted_phone.nil?
|
|
@@ -53,6 +69,8 @@ module ValidationKit
|
|
|
53
69
|
end
|
|
54
70
|
|
|
55
71
|
def format_as_phone(arg, country_code = nil, area_key = nil)
|
|
72
|
+
# Checked first, so a bad key always raises, whatever number is passed.
|
|
73
|
+
area_code = area_code_for_key(area_key)
|
|
56
74
|
country_code = country_code.to_s.strip.upcase
|
|
57
75
|
return nil if arg.blank? || country_code.blank? || !regex_for_country(country_code)
|
|
58
76
|
|
|
@@ -64,7 +82,7 @@ module ValidationKit
|
|
|
64
82
|
when /\A(?:1300|1800|1900|1902)\d{6}\z/
|
|
65
83
|
number.insert(4, " ").insert(8, " ")
|
|
66
84
|
when /\A(?:0?[12378])?[1-9][0-9]{7}\z/
|
|
67
|
-
number.insert(0,
|
|
85
|
+
number.insert(0, area_code) if /\A[1-9][0-9]{7}\z/.match?(number)
|
|
68
86
|
number.insert(0, "0") if /\A[12378][1-9][0-9]{7}\z/.match?(number)
|
|
69
87
|
|
|
70
88
|
number.insert(0, "(").insert(3, ") ").insert(9, " ")
|
|
@@ -79,7 +97,8 @@ module ValidationKit
|
|
|
79
97
|
# if it's too short
|
|
80
98
|
return nil if number.length < 10
|
|
81
99
|
|
|
82
|
-
# strip off the leading 1 (country code); any digits beyond ten are an
|
|
100
|
+
# strip off the leading 1 (country code); any digits beyond ten are an
|
|
101
|
+
# extension, unless they're unmarked (UNMARKED_US_CA_EXTENSION)
|
|
83
102
|
leading_one = number.length > 10 && number.start_with?("1")
|
|
84
103
|
number = number[1..] if leading_one
|
|
85
104
|
|
|
@@ -87,30 +106,40 @@ module ValidationKit
|
|
|
87
106
|
exchange = number[3..5]
|
|
88
107
|
sln = number[6..9]
|
|
89
108
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
109
|
+
# everything after the last digit of the number
|
|
110
|
+
national = arg[/\A(?:\D*\d){#{leading_one ? 11 : 10}}/]
|
|
111
|
+
rest = arg[national.length..]
|
|
112
|
+
|
|
113
|
+
# if letters appear among the number's digits, the extension's digits
|
|
114
|
+
# were counted as part of the number (e.g. 519 444 000 ext 123), so
|
|
115
|
+
# return nil to error out
|
|
116
|
+
return nil if number.length > 10 && national.sub(/\A\D*/, "").match?(/[[:alpha:]]/)
|
|
117
|
+
|
|
118
|
+
# Text after the number with no digits in it (e.g. "(cell)") is dropped;
|
|
119
|
+
# with digits, it's the extension, if they're marked as one.
|
|
120
|
+
extension = nil
|
|
121
|
+
if rest.match?(/\p{Nd}/)
|
|
122
|
+
return nil if UNMARKED_US_CA_EXTENSION.match?(rest)
|
|
123
|
+
|
|
124
|
+
# [[:space:]] also covers non-breaking spaces, which strip doesn't
|
|
125
|
+
extension = " #{rest.strip.sub(/\A[[:space:],;]+/, "").sub(/[[:space:]]+\z/, "")}"
|
|
101
126
|
end
|
|
102
127
|
|
|
103
128
|
"(#{area_code}) #{exchange}-#{sln}#{extension}"
|
|
104
129
|
end
|
|
105
130
|
end
|
|
106
131
|
|
|
132
|
+
# key is an AU state, in any case, as a string or symbol (always the state
|
|
133
|
+
# itself, never a method name); nil or blank means NSW/ACT. Anything else
|
|
134
|
+
# raises, rather than silently adding the NSW/ACT code (02) to, say, a
|
|
135
|
+
# Victorian number.
|
|
107
136
|
def area_code_for_key(key)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
137
|
+
state = key.to_s.strip.upcase
|
|
138
|
+
return AU_AREA_CODES["NSW"] if state.empty?
|
|
139
|
+
|
|
140
|
+
AU_AREA_CODES.fetch(state) do
|
|
141
|
+
raise ArgumentError, "area_key: #{key.inspect} isn't an Australian state or territory " \
|
|
142
|
+
"(#{AU_AREA_CODES.keys.join(", ")})"
|
|
114
143
|
end
|
|
115
144
|
end
|
|
116
145
|
end
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module ValidationKit
|
|
4
4
|
class PostalCodeValidator < ActiveModel::EachValidator
|
|
5
|
+
include CountryOption
|
|
6
|
+
|
|
5
7
|
def postal_code_regex_for_country(country_code)
|
|
6
8
|
country_code = country_code.to_s.strip.upcase
|
|
7
9
|
|
|
@@ -31,16 +33,7 @@ module ValidationKit
|
|
|
31
33
|
end
|
|
32
34
|
|
|
33
35
|
def validate_each(record, attribute, value)
|
|
34
|
-
country =
|
|
35
|
-
options[:country]
|
|
36
|
-
elsif options[:country].is_a?(Symbol) && record.respond_to?(options[:country])
|
|
37
|
-
record.public_send(options[:country])
|
|
38
|
-
elsif record.respond_to?(:country)
|
|
39
|
-
record.country
|
|
40
|
-
else
|
|
41
|
-
false
|
|
42
|
-
end
|
|
43
|
-
|
|
36
|
+
country = country_for(record)
|
|
44
37
|
return unless country
|
|
45
38
|
|
|
46
39
|
# accept "us", :US, " US " as well as "US"
|
data/lib/validation_kit.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: validation_kit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.
|
|
4
|
+
version: 2.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Wes Morgan
|
|
@@ -36,6 +36,7 @@ files:
|
|
|
36
36
|
- LICENSE
|
|
37
37
|
- README.md
|
|
38
38
|
- lib/validation_kit.rb
|
|
39
|
+
- lib/validation_kit/country_option.rb
|
|
39
40
|
- lib/validation_kit/email_validator/email_validator.rb
|
|
40
41
|
- lib/validation_kit/mixed_case_validator/config/locales/en.yml
|
|
41
42
|
- lib/validation_kit/mixed_case_validator/config/locales/fr.yml
|
|
@@ -65,7 +66,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
65
66
|
- !ruby/object:Gem::Version
|
|
66
67
|
version: '0'
|
|
67
68
|
requirements: []
|
|
68
|
-
rubygems_version: 4.0.
|
|
69
|
+
rubygems_version: 4.0.10
|
|
69
70
|
specification_version: 4
|
|
70
71
|
summary: Handy validations for Rails forms
|
|
71
72
|
test_files: []
|