israeli 0.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 +7 -0
- data/CHANGELOG.md +48 -0
- data/LICENSE.txt +21 -0
- data/README.md +187 -0
- data/Rakefile +16 -0
- data/lib/israeli/active_model/israeli_bank_account_validator.rb +34 -0
- data/lib/israeli/active_model/israeli_id_validator.rb +28 -0
- data/lib/israeli/active_model/israeli_phone_validator.rb +39 -0
- data/lib/israeli/active_model/israeli_postal_code_validator.rb +28 -0
- data/lib/israeli/errors.rb +22 -0
- data/lib/israeli/luhn.rb +35 -0
- data/lib/israeli/railtie.rb +27 -0
- data/lib/israeli/sanitizer.rb +58 -0
- data/lib/israeli/validators/bank_account.rb +105 -0
- data/lib/israeli/validators/id.rb +59 -0
- data/lib/israeli/validators/phone.rb +114 -0
- data/lib/israeli/validators/postal_code.rb +54 -0
- data/lib/israeli/version.rb +5 -0
- data/lib/israeli.rb +103 -0
- data/sig/israeli.rbs +4 -0
- metadata +72 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 775d9b34a1173bd2bbab00ca4a86f13d8fb340c13ee84d15f5893cdc7d1d6dab
|
|
4
|
+
data.tar.gz: 6b872c2ebbd7bd5573854e20f79e5bbd1ae5de265a879f370af625a0a2a00d8a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d5d9bc4d5d2b1e874221c84612307533dfc252a077c2fe5c388cfb55ca4283f39e180656353697ac38e65285a1f700bc1786f37791ff8b401f09e1252255852e
|
|
7
|
+
data.tar.gz: de83f4a136c0f53111ba505cd9165c02fac56e044a2e3d9d2819a9d459e91b4a792c50fd6b8090dc2365cc1b12de0f98d3a45225fb3685b0a2088198205c211f
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2025-12-03
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Israeli ID Validator (Mispar Zehut)**
|
|
15
|
+
- 9-digit validation with Luhn algorithm checksum
|
|
16
|
+
- Automatic left-padding for shorter inputs
|
|
17
|
+
- Format stripping (spaces, hyphens)
|
|
18
|
+
- `Israeli.valid_id?` and `Israeli.format_id` methods
|
|
19
|
+
- `IsraeliIdValidator` for ActiveModel/Rails
|
|
20
|
+
|
|
21
|
+
- **Phone Number Validator**
|
|
22
|
+
- Mobile numbers (05X prefix, 10 digits)
|
|
23
|
+
- Landline numbers (02-09 area codes, 9 digits)
|
|
24
|
+
- VoIP numbers (07X prefix, 10 digits)
|
|
25
|
+
- International format support (+972 prefix)
|
|
26
|
+
- `Israeli.valid_phone?` with `:type` option
|
|
27
|
+
- `IsraeliPhoneValidator` for ActiveModel/Rails with `:type` option
|
|
28
|
+
|
|
29
|
+
- **Postal Code Validator (Mikud)**
|
|
30
|
+
- 7-digit format validation
|
|
31
|
+
- Space separator support
|
|
32
|
+
- `Israeli.valid_postal_code?` method
|
|
33
|
+
- `IsraeliPostalCodeValidator` for ActiveModel/Rails
|
|
34
|
+
|
|
35
|
+
- **Bank Account Validator**
|
|
36
|
+
- Domestic format (13 digits: 2-3-8 structure)
|
|
37
|
+
- IBAN format with mod 97 checksum validation
|
|
38
|
+
- `Israeli.valid_bank_account?` with `:format` option
|
|
39
|
+
- `IsraeliBankAccountValidator` for ActiveModel/Rails with `:format` option
|
|
40
|
+
|
|
41
|
+
- **Core Utilities**
|
|
42
|
+
- `Israeli::Luhn` - Luhn mod 10 checksum algorithm
|
|
43
|
+
- `Israeli::Sanitizer` - Input normalization utilities
|
|
44
|
+
- `Israeli::Railtie` - Automatic Rails integration
|
|
45
|
+
|
|
46
|
+
- **Rails Integration**
|
|
47
|
+
- All validators support `allow_nil`, `allow_blank`, and `message` options
|
|
48
|
+
- Automatic loading via Railtie in Rails applications
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 dpaluy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Israeli
|
|
2
|
+
|
|
3
|
+
[](https://rubygems.org/gems/israeli)
|
|
4
|
+
[](https://github.com/dpaluy/israeli/actions/workflows/ci.yml)
|
|
5
|
+
|
|
6
|
+
Validation utilities for Israeli identifiers including ID numbers (Mispar Zehut), phone numbers, postal codes, and bank accounts.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **Israeli ID (Mispar Zehut)** - 9-digit validation with Luhn checksum
|
|
11
|
+
- **Phone Numbers** - Mobile (05X), landline (02-09), and VoIP (07X)
|
|
12
|
+
- **Postal Codes** - 7-digit Israeli postal codes (Mikud)
|
|
13
|
+
- **Bank Accounts** - Domestic (13-digit) and IBAN formats with mod 97 validation
|
|
14
|
+
- **Rails Integration** - ActiveModel validators with standard options
|
|
15
|
+
- **Zero Dependencies** - Pure Ruby, no external runtime dependencies
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
Add to your Gemfile:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
gem "israeli"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Then run:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
bundle install
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or install directly:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
gem install israeli
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
### Standalone Validation
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
require "israeli"
|
|
43
|
+
|
|
44
|
+
# Israeli ID (Mispar Zehut)
|
|
45
|
+
Israeli.valid_id?("123456782") # => true
|
|
46
|
+
Israeli.valid_id?("12345678-2") # => true (formatted input OK)
|
|
47
|
+
Israeli.valid_id?("123456789") # => false (invalid checksum)
|
|
48
|
+
|
|
49
|
+
# Phone Numbers
|
|
50
|
+
Israeli.valid_phone?("0501234567") # => true (any type)
|
|
51
|
+
Israeli.valid_phone?("0501234567", type: :mobile) # => true
|
|
52
|
+
Israeli.valid_phone?("+972501234567", type: :mobile) # => true (international format)
|
|
53
|
+
Israeli.valid_phone?("021234567", type: :landline) # => true (Jerusalem)
|
|
54
|
+
Israeli.valid_phone?("0721234567", type: :voip) # => true
|
|
55
|
+
|
|
56
|
+
# Postal Codes
|
|
57
|
+
Israeli.valid_postal_code?("2610101") # => true
|
|
58
|
+
Israeli.valid_postal_code?("26101 01") # => true (with space)
|
|
59
|
+
|
|
60
|
+
# Bank Accounts
|
|
61
|
+
Israeli.valid_bank_account?("4985622815429") # => true (domestic)
|
|
62
|
+
Israeli.valid_bank_account?("49-856-22815429") # => true (formatted)
|
|
63
|
+
Israeli.valid_bank_account?("IL620108000000099999999") # => true (IBAN)
|
|
64
|
+
Israeli.valid_bank_account?("IL62 0108 0000 0009 9999 999") # => true (spaced IBAN)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Formatting
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
# Format ID to 9 digits
|
|
71
|
+
Israeli.format_id("12345678-2") # => "123456782"
|
|
72
|
+
Israeli.format_id("12345678") # => "012345678" (pads with zero)
|
|
73
|
+
|
|
74
|
+
# Format phone numbers
|
|
75
|
+
Israeli.format_phone("0501234567") # => "050-123-4567"
|
|
76
|
+
Israeli.format_phone("0501234567", style: :international) # => "+972-501234567"
|
|
77
|
+
Israeli.format_phone("021234567") # => "02-123-4567"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Rails / ActiveModel Integration
|
|
81
|
+
|
|
82
|
+
When used in a Rails application, validators are automatically loaded via Railtie.
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
class Person < ApplicationRecord
|
|
86
|
+
validates :id_number, israeli_id: true
|
|
87
|
+
validates :mobile_phone, israeli_phone: { type: :mobile }
|
|
88
|
+
validates :postal_code, israeli_postal_code: { allow_blank: true }
|
|
89
|
+
validates :bank_account, israeli_bank_account: { format: :iban }
|
|
90
|
+
end
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### Validator Options
|
|
94
|
+
|
|
95
|
+
All validators support standard ActiveModel options:
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
# Allow nil/blank values
|
|
99
|
+
validates :id_number, israeli_id: { allow_nil: true }
|
|
100
|
+
validates :postal_code, israeli_postal_code: { allow_blank: true }
|
|
101
|
+
|
|
102
|
+
# Custom error messages
|
|
103
|
+
validates :id_number, israeli_id: { message: "is not a valid Mispar Zehut" }
|
|
104
|
+
|
|
105
|
+
# Phone type restriction
|
|
106
|
+
validates :mobile, israeli_phone: { type: :mobile } # Mobile only (05X)
|
|
107
|
+
validates :office, israeli_phone: { type: :landline } # Landline only (02-09)
|
|
108
|
+
validates :voip, israeli_phone: { type: :voip } # VoIP only (072-079)
|
|
109
|
+
|
|
110
|
+
# Bank account format restriction
|
|
111
|
+
validates :account, israeli_bank_account: { format: :domestic } # 13-digit only
|
|
112
|
+
validates :iban, israeli_bank_account: { format: :iban } # IBAN only
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Direct Validator Access
|
|
116
|
+
|
|
117
|
+
For more control, you can use the validator classes directly:
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
# ID Validator
|
|
121
|
+
Israeli::Validators::Id.valid?("123456782") # => true
|
|
122
|
+
Israeli::Validators::Id.format("12345678-2") # => "123456782"
|
|
123
|
+
|
|
124
|
+
# Phone Validator
|
|
125
|
+
Israeli::Validators::Phone.valid?("0501234567", type: :mobile) # => true
|
|
126
|
+
Israeli::Validators::Phone.mobile?("0501234567") # => true
|
|
127
|
+
Israeli::Validators::Phone.landline?("021234567") # => true
|
|
128
|
+
|
|
129
|
+
# Postal Code Validator
|
|
130
|
+
Israeli::Validators::PostalCode.valid?("2610101") # => true
|
|
131
|
+
Israeli::Validators::PostalCode.format("2610101", style: :spaced) # => "26101 01"
|
|
132
|
+
|
|
133
|
+
# Bank Account Validator
|
|
134
|
+
Israeli::Validators::BankAccount.valid?("4985622815429", format: :domestic) # => true
|
|
135
|
+
Israeli::Validators::BankAccount.valid_iban?("IL620108000000099999999") # => true
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Validation Rules
|
|
139
|
+
|
|
140
|
+
### Israeli ID (Mispar Zehut)
|
|
141
|
+
- Exactly 9 digits (shorter inputs are left-padded with zeros)
|
|
142
|
+
- Validated using Luhn algorithm (mod 10 checksum)
|
|
143
|
+
- Accepts formatted input (spaces, hyphens are stripped)
|
|
144
|
+
|
|
145
|
+
### Phone Numbers
|
|
146
|
+
|
|
147
|
+
| Type | Format | Example |
|
|
148
|
+
|------|--------|---------|
|
|
149
|
+
| Mobile | 05X-XXX-XXXX (10 digits) | 050-123-4567 |
|
|
150
|
+
| Landline | 0X-XXX-XXXX (9 digits) | 02-123-4567 |
|
|
151
|
+
| VoIP | 07X-XXX-XXXX (10 digits) | 072-123-4567 |
|
|
152
|
+
|
|
153
|
+
- International format (+972) is automatically converted to domestic format
|
|
154
|
+
- Area codes: 02 (Jerusalem), 03 (Tel Aviv), 04 (Haifa), 08 (South), 09 (Sharon)
|
|
155
|
+
|
|
156
|
+
### Postal Codes
|
|
157
|
+
- Exactly 7 digits
|
|
158
|
+
- Format validation only (no geographic range checking)
|
|
159
|
+
- Accepts with or without space separator
|
|
160
|
+
|
|
161
|
+
### Bank Accounts
|
|
162
|
+
|
|
163
|
+
| Format | Structure | Example |
|
|
164
|
+
|--------|-----------|---------|
|
|
165
|
+
| Domestic | XX-XXX-XXXXXXXX (13 digits) | 49-856-22815429 |
|
|
166
|
+
| IBAN | IL + 2 check + 19 digits (23 chars) | IL62 0108 0000 0009 9999 999 |
|
|
167
|
+
|
|
168
|
+
- IBAN validated with mod 97 checksum
|
|
169
|
+
|
|
170
|
+
## Development
|
|
171
|
+
|
|
172
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests.
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
bundle install
|
|
176
|
+
bundle exec rake test # Run tests
|
|
177
|
+
bundle exec rubocop # Run linter
|
|
178
|
+
bundle exec rake # Run both
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Contributing
|
|
182
|
+
|
|
183
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/dpaluy/israeli.
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rake/testtask"
|
|
5
|
+
|
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
|
7
|
+
t.libs << "test"
|
|
8
|
+
t.libs << "lib"
|
|
9
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
require "rubocop/rake_task"
|
|
13
|
+
|
|
14
|
+
RuboCop::RakeTask.new
|
|
15
|
+
|
|
16
|
+
task default: %i[test rubocop]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_model"
|
|
4
|
+
|
|
5
|
+
# Validates that an attribute is a valid Israeli bank account number.
|
|
6
|
+
#
|
|
7
|
+
# @example Basic usage (accepts domestic or IBAN)
|
|
8
|
+
# class BankAccount < ApplicationRecord
|
|
9
|
+
# validates :account_number, israeli_bank_account: true
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
# @example Domestic format only
|
|
13
|
+
# class BankAccount < ApplicationRecord
|
|
14
|
+
# validates :domestic_account, israeli_bank_account: { format: :domestic }
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# @example IBAN format only
|
|
18
|
+
# class BankAccount < ApplicationRecord
|
|
19
|
+
# validates :iban, israeli_bank_account: { format: :iban }
|
|
20
|
+
# end
|
|
21
|
+
class IsraeliBankAccountValidator < ActiveModel::EachValidator
|
|
22
|
+
def validate_each(record, attribute, value)
|
|
23
|
+
return if value.blank? && options[:allow_blank]
|
|
24
|
+
return if value.nil? && options[:allow_nil]
|
|
25
|
+
|
|
26
|
+
account_format = options[:format] || :any
|
|
27
|
+
return if Israeli::Validators::BankAccount.valid?(value, format: account_format)
|
|
28
|
+
|
|
29
|
+
record.errors.add(
|
|
30
|
+
attribute,
|
|
31
|
+
options[:message] || :invalid
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_model"
|
|
4
|
+
|
|
5
|
+
# Validates that an attribute is a valid Israeli ID number (Mispar Zehut).
|
|
6
|
+
#
|
|
7
|
+
# @example Basic usage
|
|
8
|
+
# class Person < ApplicationRecord
|
|
9
|
+
# validates :id_number, israeli_id: true
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
# @example With options
|
|
13
|
+
# class Person < ApplicationRecord
|
|
14
|
+
# validates :id_number, israeli_id: { allow_blank: true, message: "is not a valid Israeli ID" }
|
|
15
|
+
# end
|
|
16
|
+
class IsraeliIdValidator < ActiveModel::EachValidator
|
|
17
|
+
def validate_each(record, attribute, value)
|
|
18
|
+
return if value.blank? && options[:allow_blank]
|
|
19
|
+
return if value.nil? && options[:allow_nil]
|
|
20
|
+
|
|
21
|
+
return if Israeli::Validators::Id.valid?(value)
|
|
22
|
+
|
|
23
|
+
record.errors.add(
|
|
24
|
+
attribute,
|
|
25
|
+
options[:message] || :invalid
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_model"
|
|
4
|
+
|
|
5
|
+
# Validates that an attribute is a valid Israeli phone number.
|
|
6
|
+
#
|
|
7
|
+
# @example Basic usage (accepts mobile, landline, or VoIP)
|
|
8
|
+
# class Contact < ApplicationRecord
|
|
9
|
+
# validates :phone, israeli_phone: true
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
# @example Mobile only
|
|
13
|
+
# class Contact < ApplicationRecord
|
|
14
|
+
# validates :mobile_phone, israeli_phone: { type: :mobile }
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# @example Landline only
|
|
18
|
+
# class Contact < ApplicationRecord
|
|
19
|
+
# validates :office_phone, israeli_phone: { type: :landline }
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# @example VoIP only
|
|
23
|
+
# class Contact < ApplicationRecord
|
|
24
|
+
# validates :voip_number, israeli_phone: { type: :voip }
|
|
25
|
+
# end
|
|
26
|
+
class IsraeliPhoneValidator < ActiveModel::EachValidator
|
|
27
|
+
def validate_each(record, attribute, value)
|
|
28
|
+
return if value.blank? && options[:allow_blank]
|
|
29
|
+
return if value.nil? && options[:allow_nil]
|
|
30
|
+
|
|
31
|
+
phone_type = options[:type] || :any
|
|
32
|
+
return if Israeli::Validators::Phone.valid?(value, type: phone_type)
|
|
33
|
+
|
|
34
|
+
record.errors.add(
|
|
35
|
+
attribute,
|
|
36
|
+
options[:message] || :invalid
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_model"
|
|
4
|
+
|
|
5
|
+
# Validates that an attribute is a valid Israeli postal code (Mikud).
|
|
6
|
+
#
|
|
7
|
+
# @example Basic usage
|
|
8
|
+
# class Address < ApplicationRecord
|
|
9
|
+
# validates :postal_code, israeli_postal_code: true
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
# @example With options
|
|
13
|
+
# class Address < ApplicationRecord
|
|
14
|
+
# validates :postal_code, israeli_postal_code: { allow_blank: true }
|
|
15
|
+
# end
|
|
16
|
+
class IsraeliPostalCodeValidator < ActiveModel::EachValidator
|
|
17
|
+
def validate_each(record, attribute, value)
|
|
18
|
+
return if value.blank? && options[:allow_blank]
|
|
19
|
+
return if value.nil? && options[:allow_nil]
|
|
20
|
+
|
|
21
|
+
return if Israeli::Validators::PostalCode.valid?(value)
|
|
22
|
+
|
|
23
|
+
record.errors.add(
|
|
24
|
+
attribute,
|
|
25
|
+
options[:message] || :invalid
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Israeli
|
|
4
|
+
# Base error class for all Israeli validation errors.
|
|
5
|
+
#
|
|
6
|
+
# All validation-related exceptions inherit from this class.
|
|
7
|
+
#
|
|
8
|
+
# @example Handling errors
|
|
9
|
+
# begin
|
|
10
|
+
# Israeli.format_id!("invalid")
|
|
11
|
+
# rescue Israeli::Error => e
|
|
12
|
+
# puts "Validation failed: #{e.message}"
|
|
13
|
+
# end
|
|
14
|
+
class Error < StandardError; end
|
|
15
|
+
|
|
16
|
+
# Raised when input format is invalid for the requested validation type.
|
|
17
|
+
#
|
|
18
|
+
# @example
|
|
19
|
+
# Israeli.format_id!("abc")
|
|
20
|
+
# # => Israeli::InvalidFormatError: ID must contain only digits
|
|
21
|
+
class InvalidFormatError < Error; end
|
|
22
|
+
end
|
data/lib/israeli/luhn.rb
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Israeli
|
|
4
|
+
# Luhn algorithm (mod 10) implementation for checksum validation.
|
|
5
|
+
#
|
|
6
|
+
# Used by Israeli ID numbers (Mispar Zehut) and potentially business
|
|
7
|
+
# registration numbers. The Israeli variant uses a left-to-right
|
|
8
|
+
# processing order (index 0 = multiplier 1, index 1 = multiplier 2, etc.).
|
|
9
|
+
#
|
|
10
|
+
# @see https://en.wikipedia.org/wiki/Luhn_algorithm
|
|
11
|
+
# @see https://en.wikipedia.org/wiki/Israeli_identity_card
|
|
12
|
+
module Luhn
|
|
13
|
+
# Validates a string of digits using the Luhn algorithm.
|
|
14
|
+
#
|
|
15
|
+
# @param digits [String] A string containing only digits (0-9)
|
|
16
|
+
# @return [Boolean] true if the checksum is valid, false otherwise
|
|
17
|
+
#
|
|
18
|
+
# @example Valid ID
|
|
19
|
+
# Israeli::Luhn.valid?("123456782") # => true
|
|
20
|
+
#
|
|
21
|
+
# @example Invalid ID
|
|
22
|
+
# Israeli::Luhn.valid?("123456789") # => false
|
|
23
|
+
def self.valid?(digits)
|
|
24
|
+
return false if digits.nil? || digits.empty?
|
|
25
|
+
return false unless digits.match?(/\A\d+\z/)
|
|
26
|
+
|
|
27
|
+
sum = digits.chars.each_with_index.sum do |char, index|
|
|
28
|
+
digit = char.to_i * (index.even? ? 1 : 2)
|
|
29
|
+
digit > 9 ? digit - 9 : digit
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
(sum % 10).zero?
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Israeli
|
|
4
|
+
# Rails integration for Israeli validators.
|
|
5
|
+
#
|
|
6
|
+
# Automatically loads ActiveModel validators when used in a Rails application.
|
|
7
|
+
# The validators are loaded via ActiveSupport's lazy load hooks to ensure
|
|
8
|
+
# proper initialization timing.
|
|
9
|
+
#
|
|
10
|
+
# @example Usage in a Rails model
|
|
11
|
+
# class Person < ApplicationRecord
|
|
12
|
+
# validates :id_number, israeli_id: true
|
|
13
|
+
# validates :phone, israeli_phone: { type: :mobile }
|
|
14
|
+
# validates :postal_code, israeli_postal_code: { allow_blank: true }
|
|
15
|
+
# validates :bank_account, israeli_bank_account: { format: :iban }
|
|
16
|
+
# end
|
|
17
|
+
class Railtie < Rails::Railtie
|
|
18
|
+
initializer "israeli.active_model" do
|
|
19
|
+
ActiveSupport.on_load(:active_model) do
|
|
20
|
+
require "israeli/active_model/israeli_id_validator"
|
|
21
|
+
require "israeli/active_model/israeli_postal_code_validator"
|
|
22
|
+
require "israeli/active_model/israeli_phone_validator"
|
|
23
|
+
require "israeli/active_model/israeli_bank_account_validator"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Israeli
|
|
4
|
+
# Input sanitization utilities for Israeli validators.
|
|
5
|
+
#
|
|
6
|
+
# Provides consistent normalization of user input across all validators,
|
|
7
|
+
# handling common formatting variations like spaces, hyphens, and
|
|
8
|
+
# international phone prefixes.
|
|
9
|
+
module Sanitizer
|
|
10
|
+
# Common separator characters to strip from input
|
|
11
|
+
STRIP_CHARS = /[\s\-.+]/
|
|
12
|
+
|
|
13
|
+
# Extracts only digit characters from input.
|
|
14
|
+
#
|
|
15
|
+
# Strips common formatting characters (spaces, hyphens, dots) and
|
|
16
|
+
# converts the input to a string. Returns nil for nil input.
|
|
17
|
+
#
|
|
18
|
+
# @param value [String, Integer, nil] The value to sanitize
|
|
19
|
+
# @return [String, nil] String containing only digits, or nil
|
|
20
|
+
#
|
|
21
|
+
# @example Basic usage
|
|
22
|
+
# Sanitizer.digits_only("123-456-789") # => "123456789"
|
|
23
|
+
# Sanitizer.digits_only("12 34 56") # => "123456"
|
|
24
|
+
# Sanitizer.digits_only(123456) # => "123456"
|
|
25
|
+
# Sanitizer.digits_only(nil) # => nil
|
|
26
|
+
def self.digits_only(value)
|
|
27
|
+
return nil if value.nil?
|
|
28
|
+
|
|
29
|
+
value.to_s.gsub(STRIP_CHARS, "")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Normalizes phone numbers by stripping international prefixes.
|
|
33
|
+
#
|
|
34
|
+
# Handles common Israeli international dialing formats:
|
|
35
|
+
# - +972 (standard international)
|
|
36
|
+
# - 972 (without plus)
|
|
37
|
+
# - 00972 (international access code)
|
|
38
|
+
#
|
|
39
|
+
# @param value [String, nil] The phone number to normalize
|
|
40
|
+
# @return [String, nil] Normalized domestic phone number, or nil
|
|
41
|
+
#
|
|
42
|
+
# @example International formats
|
|
43
|
+
# Sanitizer.normalize_phone("+972501234567") # => "0501234567"
|
|
44
|
+
# Sanitizer.normalize_phone("972-50-123-4567") # => "0501234567"
|
|
45
|
+
# Sanitizer.normalize_phone("00972501234567") # => "0501234567"
|
|
46
|
+
#
|
|
47
|
+
# @example Domestic formats (unchanged)
|
|
48
|
+
# Sanitizer.normalize_phone("050-123-4567") # => "0501234567"
|
|
49
|
+
# Sanitizer.normalize_phone("0501234567") # => "0501234567"
|
|
50
|
+
def self.normalize_phone(value)
|
|
51
|
+
cleaned = digits_only(value)
|
|
52
|
+
return nil if cleaned.nil?
|
|
53
|
+
|
|
54
|
+
# Strip international prefixes and add leading zero
|
|
55
|
+
cleaned.sub(/\A(00972|972)/, "0")
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Israeli
|
|
4
|
+
module Validators
|
|
5
|
+
# Validates Israeli bank account numbers.
|
|
6
|
+
#
|
|
7
|
+
# Supports two formats:
|
|
8
|
+
# - Domestic: 13 digits (2-digit bank + 3-digit branch + 8-digit account)
|
|
9
|
+
# - IBAN: 23 characters (IL + 2 check digits + 19 BBAN digits)
|
|
10
|
+
#
|
|
11
|
+
# @example Domestic validation
|
|
12
|
+
# Israeli::Validators::BankAccount.valid?("4985622815429") # => true
|
|
13
|
+
# Israeli::Validators::BankAccount.valid?("49-856-22815429") # => true
|
|
14
|
+
#
|
|
15
|
+
# @example IBAN validation
|
|
16
|
+
# Israeli::Validators::BankAccount.valid?("IL620108000000099999999") # => true
|
|
17
|
+
#
|
|
18
|
+
# @see https://www.ecbs.org/iban/israel-bank-account-number.html
|
|
19
|
+
class BankAccount
|
|
20
|
+
# Domestic format: 2 (bank) + 3 (branch) + 8 (account) = 13 digits
|
|
21
|
+
DOMESTIC_PATTERN = /\A\d{13}\z/
|
|
22
|
+
|
|
23
|
+
# IBAN format: IL + 2 check digits + 19 BBAN digits = 23 characters
|
|
24
|
+
IBAN_PATTERN = /\AIL\d{21}\z/
|
|
25
|
+
|
|
26
|
+
# Validates an Israeli bank account number.
|
|
27
|
+
#
|
|
28
|
+
# @param value [String, nil] The bank account to validate
|
|
29
|
+
# @param format [Symbol] Format to validate: :domestic, :iban, or :any
|
|
30
|
+
# @return [Boolean] true if valid, false otherwise
|
|
31
|
+
def self.valid?(value, format: :any)
|
|
32
|
+
return false if value.nil?
|
|
33
|
+
|
|
34
|
+
case format
|
|
35
|
+
when :domestic
|
|
36
|
+
valid_domestic?(Sanitizer.digits_only(value))
|
|
37
|
+
when :iban
|
|
38
|
+
valid_iban?(normalize_iban(value))
|
|
39
|
+
when :any
|
|
40
|
+
valid_domestic?(Sanitizer.digits_only(value)) ||
|
|
41
|
+
valid_iban?(normalize_iban(value))
|
|
42
|
+
else
|
|
43
|
+
false
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Validates a domestic Israeli bank account (13 digits).
|
|
48
|
+
#
|
|
49
|
+
# @param digits [String, nil] Digits-only bank account
|
|
50
|
+
# @return [Boolean]
|
|
51
|
+
def self.valid_domestic?(digits)
|
|
52
|
+
return false if digits.nil?
|
|
53
|
+
|
|
54
|
+
digits.match?(DOMESTIC_PATTERN)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Validates an Israeli IBAN with mod 97 checksum.
|
|
58
|
+
#
|
|
59
|
+
# @param iban [String, nil] Normalized IBAN (uppercase, no spaces)
|
|
60
|
+
# @return [Boolean]
|
|
61
|
+
def self.valid_iban?(iban)
|
|
62
|
+
return false if iban.nil?
|
|
63
|
+
return false unless iban.match?(IBAN_PATTERN)
|
|
64
|
+
|
|
65
|
+
# IBAN mod 97 validation
|
|
66
|
+
# 1. Move first 4 chars to end
|
|
67
|
+
# 2. Convert letters to numbers (A=10, B=11, ..., Z=35)
|
|
68
|
+
# 3. Calculate mod 97, must equal 1
|
|
69
|
+
rearranged = iban[4..] + iban[0..3]
|
|
70
|
+
numeric = rearranged.gsub(/[A-Z]/) { |c| (c.ord - 55).to_s }
|
|
71
|
+
numeric.to_i % 97 == 1
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Formats a bank account to a specified style.
|
|
75
|
+
#
|
|
76
|
+
# @param value [String, nil] The bank account to format
|
|
77
|
+
# @param style [Symbol] :domestic (XX-XXX-XXXXXXXX) or :iban
|
|
78
|
+
# @return [String, nil] Formatted bank account, or nil if invalid
|
|
79
|
+
#
|
|
80
|
+
# @example
|
|
81
|
+
# BankAccount.format("4985622815429") # => "49-856-22815429"
|
|
82
|
+
# BankAccount.format("4985622815429", style: :compact) # => "4985622815429"
|
|
83
|
+
def self.format(value, style: :domestic)
|
|
84
|
+
digits = Sanitizer.digits_only(value)
|
|
85
|
+
|
|
86
|
+
if valid_domestic?(digits)
|
|
87
|
+
case style
|
|
88
|
+
when :domestic
|
|
89
|
+
"#{digits[0..1]}-#{digits[2..4]}-#{digits[5..12]}"
|
|
90
|
+
else
|
|
91
|
+
digits
|
|
92
|
+
end
|
|
93
|
+
elsif valid_iban?(normalize_iban(value))
|
|
94
|
+
normalize_iban(value)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @private
|
|
99
|
+
def self.normalize_iban(value)
|
|
100
|
+
value.to_s.gsub(/\s/, "").upcase
|
|
101
|
+
end
|
|
102
|
+
private_class_method :normalize_iban
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Israeli
|
|
4
|
+
module Validators
|
|
5
|
+
# Validates Israeli ID numbers (Mispar Zehut / Teudat Zehut).
|
|
6
|
+
#
|
|
7
|
+
# Israeli ID numbers are 9-digit numbers where the last digit is a
|
|
8
|
+
# check digit calculated using the Luhn algorithm (mod 10).
|
|
9
|
+
#
|
|
10
|
+
# @example Basic validation
|
|
11
|
+
# Israeli::Validators::Id.valid?("123456782") # => true
|
|
12
|
+
# Israeli::Validators::Id.valid?("123456789") # => false
|
|
13
|
+
#
|
|
14
|
+
# @example With formatting
|
|
15
|
+
# Israeli::Validators::Id.valid?("12345678-2") # => true
|
|
16
|
+
# Israeli::Validators::Id.valid?("012345678") # => true (leading zero)
|
|
17
|
+
#
|
|
18
|
+
# @see https://en.wikipedia.org/wiki/Israeli_identity_card
|
|
19
|
+
class Id
|
|
20
|
+
# Validates an Israeli ID number.
|
|
21
|
+
#
|
|
22
|
+
# Accepts formatted input (with hyphens/spaces) and handles leading zeros.
|
|
23
|
+
# IDs shorter than 9 digits are automatically left-padded with zeros.
|
|
24
|
+
#
|
|
25
|
+
# @param value [String, Integer, nil] The ID number to validate
|
|
26
|
+
# @return [Boolean] true if valid, false otherwise
|
|
27
|
+
def self.valid?(value)
|
|
28
|
+
digits = Sanitizer.digits_only(value)
|
|
29
|
+
return false if digits.nil? || digits.empty?
|
|
30
|
+
|
|
31
|
+
# Left-pad to 9 digits if shorter (handles IDs like "12345678")
|
|
32
|
+
padded = digits.rjust(9, "0")
|
|
33
|
+
|
|
34
|
+
# Must be exactly 9 digits after padding
|
|
35
|
+
return false unless padded.match?(/\A\d{9}\z/)
|
|
36
|
+
|
|
37
|
+
Luhn.valid?(padded)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Formats an Israeli ID to standard 9-digit format.
|
|
41
|
+
#
|
|
42
|
+
# @param value [String, Integer, nil] The ID number to format
|
|
43
|
+
# @return [String, nil] 9-digit formatted ID, or nil if invalid
|
|
44
|
+
#
|
|
45
|
+
# @example
|
|
46
|
+
# Israeli::Validators::Id.format("12345678-2") # => "123456782"
|
|
47
|
+
# Israeli::Validators::Id.format(12345678) # => "012345678"
|
|
48
|
+
def self.format(value)
|
|
49
|
+
digits = Sanitizer.digits_only(value)
|
|
50
|
+
return nil if digits.nil? || digits.empty?
|
|
51
|
+
|
|
52
|
+
padded = digits.rjust(9, "0")
|
|
53
|
+
return nil unless valid?(padded)
|
|
54
|
+
|
|
55
|
+
padded
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Israeli
|
|
4
|
+
module Validators
|
|
5
|
+
# Validates Israeli phone numbers.
|
|
6
|
+
#
|
|
7
|
+
# Supports three types of Israeli phone numbers:
|
|
8
|
+
# - Mobile: 10 digits starting with 05X (050, 052, 053, 054, 055, 058)
|
|
9
|
+
# - Landline: 9 digits starting with 0X (02, 03, 04, 08, 09)
|
|
10
|
+
# - VoIP: 10 digits starting with 07X (072-079)
|
|
11
|
+
#
|
|
12
|
+
# Automatically handles international format (+972) conversion.
|
|
13
|
+
#
|
|
14
|
+
# @example Mobile validation
|
|
15
|
+
# Israeli::Validators::Phone.valid?("0501234567") # => true
|
|
16
|
+
# Israeli::Validators::Phone.valid?("+972501234567") # => true
|
|
17
|
+
# Israeli::Validators::Phone.valid?("050-123-4567") # => true
|
|
18
|
+
#
|
|
19
|
+
# @example Landline validation
|
|
20
|
+
# Israeli::Validators::Phone.valid?("021234567", type: :landline) # => true
|
|
21
|
+
# Israeli::Validators::Phone.valid?("03-123-4567") # => true
|
|
22
|
+
#
|
|
23
|
+
# @see https://en.wikipedia.org/wiki/Telephone_numbers_in_Israel
|
|
24
|
+
class Phone
|
|
25
|
+
# Mobile phone pattern: 05X followed by 7 more digits (10 total)
|
|
26
|
+
MOBILE_PATTERN = /\A05\d{8}\z/
|
|
27
|
+
|
|
28
|
+
# Landline pattern: 0X followed by 7 digits (9 total)
|
|
29
|
+
# Valid area codes: 02 (Jerusalem), 03 (Tel Aviv), 04 (Haifa), 08 (South), 09 (Sharon)
|
|
30
|
+
LANDLINE_PATTERN = /\A0[2-489]\d{7}\z/
|
|
31
|
+
|
|
32
|
+
# VoIP pattern: 07X (X=2-9) followed by 7 digits (10 total)
|
|
33
|
+
VOIP_PATTERN = /\A07[2-9]\d{7}\z/
|
|
34
|
+
|
|
35
|
+
# Validates an Israeli phone number.
|
|
36
|
+
#
|
|
37
|
+
# @param value [String, nil] The phone number to validate
|
|
38
|
+
# @param type [Symbol] Type to validate: :mobile, :landline, :voip, or :any
|
|
39
|
+
# @return [Boolean] true if valid, false otherwise
|
|
40
|
+
def self.valid?(value, type: :any)
|
|
41
|
+
normalized = Sanitizer.normalize_phone(value)
|
|
42
|
+
return false if normalized.nil? || normalized.empty?
|
|
43
|
+
|
|
44
|
+
case type
|
|
45
|
+
when :mobile then mobile?(normalized)
|
|
46
|
+
when :landline then landline?(normalized)
|
|
47
|
+
when :voip then voip?(normalized)
|
|
48
|
+
when :any then mobile?(normalized) || landline?(normalized) || voip?(normalized)
|
|
49
|
+
else false
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Checks if the number is a valid mobile number.
|
|
54
|
+
#
|
|
55
|
+
# @param value [String] Normalized phone number
|
|
56
|
+
# @return [Boolean]
|
|
57
|
+
def self.mobile?(value)
|
|
58
|
+
value.match?(MOBILE_PATTERN)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Checks if the number is a valid landline number.
|
|
62
|
+
#
|
|
63
|
+
# @param value [String] Normalized phone number
|
|
64
|
+
# @return [Boolean]
|
|
65
|
+
def self.landline?(value)
|
|
66
|
+
value.match?(LANDLINE_PATTERN)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Checks if the number is a valid VoIP number.
|
|
70
|
+
#
|
|
71
|
+
# @param value [String] Normalized phone number
|
|
72
|
+
# @return [Boolean]
|
|
73
|
+
def self.voip?(value)
|
|
74
|
+
value.match?(VOIP_PATTERN)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Formats a phone number.
|
|
78
|
+
#
|
|
79
|
+
# @param value [String, nil] The phone number to format
|
|
80
|
+
# @param style [Symbol] :dashed, :international, or :compact
|
|
81
|
+
# @return [String, nil] Formatted phone number, or nil if invalid
|
|
82
|
+
#
|
|
83
|
+
# @example
|
|
84
|
+
# Phone.format("0501234567") # => "050-123-4567"
|
|
85
|
+
# Phone.format("0501234567", style: :international) # => "+972-50-123-4567"
|
|
86
|
+
# Phone.format("021234567") # => "02-123-4567"
|
|
87
|
+
def self.format(value, style: :dashed)
|
|
88
|
+
normalized = Sanitizer.normalize_phone(value)
|
|
89
|
+
return nil unless valid?(normalized)
|
|
90
|
+
|
|
91
|
+
case style
|
|
92
|
+
when :dashed
|
|
93
|
+
format_dashed(normalized)
|
|
94
|
+
when :international
|
|
95
|
+
"+972-#{normalized[1..]}"
|
|
96
|
+
else
|
|
97
|
+
normalized
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# @private
|
|
102
|
+
def self.format_dashed(normalized)
|
|
103
|
+
if mobile?(normalized) || voip?(normalized)
|
|
104
|
+
# 10-digit format: XXX-XXX-XXXX
|
|
105
|
+
"#{normalized[0..2]}-#{normalized[3..5]}-#{normalized[6..9]}"
|
|
106
|
+
else
|
|
107
|
+
# 9-digit landline format: XX-XXX-XXXX
|
|
108
|
+
"#{normalized[0..1]}-#{normalized[2..4]}-#{normalized[5..8]}"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
private_class_method :format_dashed
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Israeli
|
|
4
|
+
module Validators
|
|
5
|
+
# Validates Israeli postal codes (Mikud).
|
|
6
|
+
#
|
|
7
|
+
# Israeli postal codes consist of exactly 7 digits. They are organized
|
|
8
|
+
# geographically from north to south, with the first 2 digits indicating
|
|
9
|
+
# the postal area. Jerusalem postal codes start with 9 despite its
|
|
10
|
+
# central location.
|
|
11
|
+
#
|
|
12
|
+
# @example Basic validation
|
|
13
|
+
# Israeli::Validators::PostalCode.valid?("2610101") # => true
|
|
14
|
+
# Israeli::Validators::PostalCode.valid?("26101 01") # => true (with space)
|
|
15
|
+
#
|
|
16
|
+
# @example Regional examples
|
|
17
|
+
# Israeli::Validators::PostalCode.valid?("1029200") # => true (Metula, north)
|
|
18
|
+
# Israeli::Validators::PostalCode.valid?("8800000") # => true (Eilat, south)
|
|
19
|
+
# Israeli::Validators::PostalCode.valid?("9100000") # => true (Jerusalem)
|
|
20
|
+
#
|
|
21
|
+
# @see https://globepostalcodes.com/israel
|
|
22
|
+
class PostalCode
|
|
23
|
+
# Validates an Israeli postal code.
|
|
24
|
+
#
|
|
25
|
+
# @param value [String, nil] The postal code to validate
|
|
26
|
+
# @return [Boolean] true if valid 7-digit format, false otherwise
|
|
27
|
+
def self.valid?(value)
|
|
28
|
+
digits = Sanitizer.digits_only(value)
|
|
29
|
+
return false if digits.nil?
|
|
30
|
+
|
|
31
|
+
digits.match?(/\A\d{7}\z/)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Formats a postal code to standard representation.
|
|
35
|
+
#
|
|
36
|
+
# @param value [String, nil] The postal code to format
|
|
37
|
+
# @param style [Symbol] :compact (7 digits) or :spaced (5+2 format)
|
|
38
|
+
# @return [String, nil] Formatted postal code, or nil if invalid
|
|
39
|
+
#
|
|
40
|
+
# @example
|
|
41
|
+
# Israeli::Validators::PostalCode.format("26101 01") # => "2610101"
|
|
42
|
+
# Israeli::Validators::PostalCode.format("2610101", style: :spaced) # => "26101 01"
|
|
43
|
+
def self.format(value, style: :compact)
|
|
44
|
+
digits = Sanitizer.digits_only(value)
|
|
45
|
+
return nil unless valid?(digits)
|
|
46
|
+
|
|
47
|
+
case style
|
|
48
|
+
when :spaced then "#{digits[0..4]} #{digits[5..6]}"
|
|
49
|
+
else digits
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/israeli.rb
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "israeli/version"
|
|
4
|
+
require_relative "israeli/errors"
|
|
5
|
+
require_relative "israeli/luhn"
|
|
6
|
+
require_relative "israeli/sanitizer"
|
|
7
|
+
|
|
8
|
+
# Main namespace for the Israeli validators gem.
|
|
9
|
+
#
|
|
10
|
+
# Provides validation utilities for Israeli identifiers including ID numbers
|
|
11
|
+
# (Mispar Zehut), postal codes, phone numbers, and bank accounts.
|
|
12
|
+
#
|
|
13
|
+
# @example Validate an Israeli ID
|
|
14
|
+
# Israeli.valid_id?("123456782") # => true
|
|
15
|
+
#
|
|
16
|
+
# @example Validate a phone number
|
|
17
|
+
# Israeli.valid_phone?("0501234567", type: :mobile) # => true
|
|
18
|
+
#
|
|
19
|
+
# @see Israeli::Validators::Id
|
|
20
|
+
# @see Israeli::Validators::Phone
|
|
21
|
+
# @see Israeli::Validators::PostalCode
|
|
22
|
+
# @see Israeli::Validators::BankAccount
|
|
23
|
+
module Israeli
|
|
24
|
+
class << self
|
|
25
|
+
# Validates an Israeli ID number (Mispar Zehut).
|
|
26
|
+
#
|
|
27
|
+
# @param value [String, Integer] The ID number to validate
|
|
28
|
+
# @return [Boolean] true if valid, false otherwise
|
|
29
|
+
#
|
|
30
|
+
# @example
|
|
31
|
+
# Israeli.valid_id?("123456782") # => true
|
|
32
|
+
# Israeli.valid_id?("12345678-2") # => true (formatted)
|
|
33
|
+
# Israeli.valid_id?("123456789") # => false (bad checksum)
|
|
34
|
+
def valid_id?(value)
|
|
35
|
+
Validators::Id.valid?(value)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Validates an Israeli postal code (Mikud).
|
|
39
|
+
#
|
|
40
|
+
# @param value [String] The postal code to validate
|
|
41
|
+
# @return [Boolean] true if valid, false otherwise
|
|
42
|
+
#
|
|
43
|
+
# @example
|
|
44
|
+
# Israeli.valid_postal_code?("2610101") # => true
|
|
45
|
+
# Israeli.valid_postal_code?("26101 01") # => true (with space)
|
|
46
|
+
def valid_postal_code?(value)
|
|
47
|
+
Validators::PostalCode.valid?(value)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Validates an Israeli phone number.
|
|
51
|
+
#
|
|
52
|
+
# @param value [String] The phone number to validate
|
|
53
|
+
# @param type [Symbol] Type of phone: :mobile, :landline, :voip, or :any
|
|
54
|
+
# @return [Boolean] true if valid, false otherwise
|
|
55
|
+
#
|
|
56
|
+
# @example
|
|
57
|
+
# Israeli.valid_phone?("0501234567") # => true
|
|
58
|
+
# Israeli.valid_phone?("0501234567", type: :mobile) # => true
|
|
59
|
+
# Israeli.valid_phone?("+972501234567", type: :mobile) # => true
|
|
60
|
+
def valid_phone?(value, type: :any)
|
|
61
|
+
Validators::Phone.valid?(value, type: type)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Validates an Israeli bank account number.
|
|
65
|
+
#
|
|
66
|
+
# @param value [String] The bank account to validate
|
|
67
|
+
# @param format [Symbol] Format: :domestic, :iban, or :any
|
|
68
|
+
# @return [Boolean] true if valid, false otherwise
|
|
69
|
+
#
|
|
70
|
+
# @example
|
|
71
|
+
# Israeli.valid_bank_account?("4985622815429") # => true (domestic)
|
|
72
|
+
# Israeli.valid_bank_account?("IL620108000000099999999") # => true (IBAN)
|
|
73
|
+
def valid_bank_account?(value, format: :any)
|
|
74
|
+
Validators::BankAccount.valid?(value, format: format)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Formats an Israeli ID number to standard 9-digit format.
|
|
78
|
+
#
|
|
79
|
+
# @param value [String, Integer] The ID number to format
|
|
80
|
+
# @return [String, nil] Formatted ID or nil if invalid
|
|
81
|
+
def format_id(value)
|
|
82
|
+
Validators::Id.format(value)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Formats an Israeli phone number.
|
|
86
|
+
#
|
|
87
|
+
# @param value [String] The phone number to format
|
|
88
|
+
# @param style [Symbol] Format style: :dashed, :international, or :compact
|
|
89
|
+
# @return [String, nil] Formatted phone or nil if invalid
|
|
90
|
+
def format_phone(value, style: :dashed)
|
|
91
|
+
Validators::Phone.format(value, style: style)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Require validators after module definition to avoid circular dependency
|
|
97
|
+
require_relative "israeli/validators/id"
|
|
98
|
+
require_relative "israeli/validators/postal_code"
|
|
99
|
+
require_relative "israeli/validators/phone"
|
|
100
|
+
require_relative "israeli/validators/bank_account"
|
|
101
|
+
|
|
102
|
+
# Load Rails integration if Rails is available
|
|
103
|
+
require_relative "israeli/railtie" if defined?(Rails::Railtie)
|
data/sig/israeli.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: israeli
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- dpaluy
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: Israeli provides validators for Israeli identifiers including Mispar
|
|
13
|
+
Zehut (ID numbers), phone numbers (mobile, landline, VoIP), postal codes, and bank
|
|
14
|
+
accounts (domestic and IBAN). Includes both standalone validators and ActiveModel/Rails
|
|
15
|
+
integration.
|
|
16
|
+
email:
|
|
17
|
+
- dpaluy@users.noreply.github.com
|
|
18
|
+
executables: []
|
|
19
|
+
extensions: []
|
|
20
|
+
extra_rdoc_files:
|
|
21
|
+
- CHANGELOG.md
|
|
22
|
+
- LICENSE.txt
|
|
23
|
+
- README.md
|
|
24
|
+
files:
|
|
25
|
+
- CHANGELOG.md
|
|
26
|
+
- LICENSE.txt
|
|
27
|
+
- README.md
|
|
28
|
+
- Rakefile
|
|
29
|
+
- lib/israeli.rb
|
|
30
|
+
- lib/israeli/active_model/israeli_bank_account_validator.rb
|
|
31
|
+
- lib/israeli/active_model/israeli_id_validator.rb
|
|
32
|
+
- lib/israeli/active_model/israeli_phone_validator.rb
|
|
33
|
+
- lib/israeli/active_model/israeli_postal_code_validator.rb
|
|
34
|
+
- lib/israeli/errors.rb
|
|
35
|
+
- lib/israeli/luhn.rb
|
|
36
|
+
- lib/israeli/railtie.rb
|
|
37
|
+
- lib/israeli/sanitizer.rb
|
|
38
|
+
- lib/israeli/validators/bank_account.rb
|
|
39
|
+
- lib/israeli/validators/id.rb
|
|
40
|
+
- lib/israeli/validators/phone.rb
|
|
41
|
+
- lib/israeli/validators/postal_code.rb
|
|
42
|
+
- lib/israeli/version.rb
|
|
43
|
+
- sig/israeli.rbs
|
|
44
|
+
homepage: https://github.com/dpaluy/israeli
|
|
45
|
+
licenses:
|
|
46
|
+
- MIT
|
|
47
|
+
metadata:
|
|
48
|
+
rubygems_mfa_required: 'true'
|
|
49
|
+
homepage_uri: https://github.com/dpaluy/israeli
|
|
50
|
+
documentation_uri: https://rubydoc.info/gems/israeli
|
|
51
|
+
source_code_uri: https://github.com/dpaluy/israeli
|
|
52
|
+
changelog_uri: https://github.com/dpaluy/israeli/blob/main/CHANGELOG.md
|
|
53
|
+
bug_tracker_uri: https://github.com/dpaluy/israeli/issues
|
|
54
|
+
rdoc_options: []
|
|
55
|
+
require_paths:
|
|
56
|
+
- lib
|
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: 3.2.0
|
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '0'
|
|
67
|
+
requirements: []
|
|
68
|
+
rubygems_version: 4.0.0
|
|
69
|
+
specification_version: 4
|
|
70
|
+
summary: Validation utilities for Israeli identifiers (ID, phone, postal code, bank
|
|
71
|
+
account).
|
|
72
|
+
test_files: []
|