addressing 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -1
- data/README.md +18 -2
- data/data/address_formats.dump +0 -0
- data/data/address_formats.json +1 -1
- data/data/country/zh.json +1 -1
- data/data/library_customizations.rb +1 -1
- data/lib/addressing/address_field.rb +0 -2
- data/lib/addressing/address_format.rb +21 -0
- data/lib/addressing/administrative_area_type.rb +0 -2
- data/lib/addressing/dependent_locality_type.rb +0 -2
- data/lib/addressing/enum.rb +2 -0
- data/lib/addressing/field_override.rb +33 -0
- data/lib/addressing/lazy_subdivisions.rb +7 -0
- data/lib/addressing/locality_type.rb +0 -2
- data/lib/addressing/model.rb +111 -0
- data/lib/addressing/pattern_type.rb +0 -2
- data/lib/addressing/postal_code_type.rb +0 -2
- data/lib/addressing/subdivision.rb +1 -1
- data/lib/addressing/version.rb +1 -1
- data/lib/addressing.rb +10 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b46d807aa97c2e089222447115d7862105246f37f850e65f824f8599af5d714d
|
4
|
+
data.tar.gz: b2f82f834eb10ead8e14e9b63083093b94a27bdec7a157a7102ee6c01eb15e6a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fb86d0f70a24db611837a597b3f8846a545f9c16d0df2099d6baab0235cc27ff0730b97f55b138b1d946715e3a9a671b6af23544ba59851d6f10b1cde428d9e5
|
7
|
+
data.tar.gz: 4b4c5ce9982c8faaf53a6eab5117b8250d17fc3e11ffb34e24e3765ad931b5ad1fbd4031633a132c23c7c4d489131ff5364af22a0d695acad02e349b23414d25
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,20 @@
|
|
2
2
|
|
3
3
|
All notable changes to `addressing` will be documented in this file.
|
4
4
|
|
5
|
-
## 0.
|
5
|
+
## 0.3.0 (04-04-2022)
|
6
|
+
|
7
|
+
- Allow field validation to be overridden
|
8
|
+
- Only verify address when one of the field changes
|
9
|
+
- Allow used address fields to be configured
|
10
|
+
|
11
|
+
## 0.2.0 (25-02-2022)
|
12
|
+
|
13
|
+
- Add ActiveRecord validations
|
14
|
+
|
15
|
+
## 0.1.1 (21-02-2022)
|
16
|
+
|
17
|
+
- Fix typo in DE custom format
|
18
|
+
|
19
|
+
## 0.1.0 (07-02-2022)
|
6
20
|
|
7
21
|
- Initial release
|
data/README.md
CHANGED
@@ -172,6 +172,24 @@ p formatter.format(address, origin_country: "FR")
|
|
172
172
|
# ÉTATS-UNIS - UNITED STATES
|
173
173
|
```
|
174
174
|
|
175
|
+
### Validating addresses
|
176
|
+
|
177
|
+
For Active Record models, use:
|
178
|
+
|
179
|
+
```rb
|
180
|
+
class User < ApplicationRecord
|
181
|
+
validates_address_format
|
182
|
+
end
|
183
|
+
```
|
184
|
+
|
185
|
+
For performance, the address is only verified if at least one of the fields changes. Set your own condition with:
|
186
|
+
|
187
|
+
```rb
|
188
|
+
class User < ApplicationRecord
|
189
|
+
validates_address if: -> { something_changed? }, ...
|
190
|
+
end
|
191
|
+
```
|
192
|
+
|
175
193
|
## Changelog
|
176
194
|
|
177
195
|
Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
|
@@ -203,5 +221,3 @@ Feel free to open an issue to get feedback on your idea before spending too much
|
|
203
221
|
## License
|
204
222
|
|
205
223
|
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
|
206
|
-
|
207
|
-
https://github.com/countries/countries/blob/eddb4af3889e5c8b66d897f9789d43c4a1ee0598/spec/thread_safety_spec.rb
|
data/data/address_formats.dump
CHANGED
Binary file
|
data/data/address_formats.json
CHANGED
@@ -42,7 +42,7 @@
|
|
42
42
|
{"country_code": "CX","format": "%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%locality %administrative_area %postal_code","uppercase_fields": ["locality","administrative_area"],"postal_code_pattern": "6798"}
|
43
43
|
{"country_code": "CY","format": "%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%postal_code %locality","postal_code_pattern": "\\d{4}"}
|
44
44
|
{"country_code": "CZ","format": "%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%postal_code %locality","required_fields": ["address_line1","locality","postal_code"],"postal_code_pattern": "\\d{3} ?\\d{2}"}
|
45
|
-
{"country_code": "DE","format": "%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%
|
45
|
+
{"country_code": "DE","format": "%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%postal_code %locality","required_fields": ["address_line1","locality","postal_code"],"postal_code_pattern": "\\d{5}"}
|
46
46
|
{"country_code": "DK","format": "%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%postal_code %locality","required_fields": ["address_line1","locality","postal_code"],"postal_code_pattern": "\\d{4}"}
|
47
47
|
{"country_code": "DO","format": "%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%postal_code %locality","postal_code_pattern": "\\d{5}"}
|
48
48
|
{"country_code": "DZ","format": "%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%postal_code %locality","postal_code_pattern": "\\d{5}"}
|
data/data/country/zh.json
CHANGED
@@ -35,7 +35,6 @@
|
|
35
35
|
"MK": "北马其顿",
|
36
36
|
"BJ": "贝宁",
|
37
37
|
"BE": "比利时",
|
38
|
-
"PE": "秘鲁",
|
39
38
|
"IS": "冰岛",
|
40
39
|
"PR": "波多黎各",
|
41
40
|
"PL": "波兰",
|
@@ -149,6 +148,7 @@
|
|
149
148
|
"MN": "蒙古",
|
150
149
|
"MS": "蒙特塞拉特",
|
151
150
|
"BD": "孟加拉国",
|
151
|
+
"PE": "秘鲁",
|
152
152
|
"FM": "密克罗尼西亚",
|
153
153
|
"MM": "缅甸",
|
154
154
|
"MD": "摩尔多瓦",
|
@@ -24,7 +24,7 @@ def address_format_customizations(country_code)
|
|
24
24
|
# Switch %organization and %recipient.
|
25
25
|
# https://github.com/googlei18n/libaddressinput/issues/83
|
26
26
|
format_customizations["DE"] = {
|
27
|
-
"format" => "%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%
|
27
|
+
"format" => "%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%postal_code %locality"
|
28
28
|
}
|
29
29
|
|
30
30
|
# Revert the removal of %sortingCode.
|
@@ -141,4 +141,25 @@ module Addressing
|
|
141
141
|
fields & used_fields
|
142
142
|
end
|
143
143
|
end
|
144
|
+
|
145
|
+
class AddressFormatHelper
|
146
|
+
class << self
|
147
|
+
# Gets the required fields.
|
148
|
+
#
|
149
|
+
# Applies field overrides to the required fields
|
150
|
+
# specified by the address format.
|
151
|
+
def required_fields(address_format, field_overrides)
|
152
|
+
required_fields = address_format.required_fields
|
153
|
+
required_fields = required_fields - field_overrides.optional_fields
|
154
|
+
required_fields = required_fields - field_overrides.hidden_fields
|
155
|
+
|
156
|
+
if field_overrides.required_fields
|
157
|
+
required_fields = required_fields + field_overrides.required_fields
|
158
|
+
required_fields = required_fields.uniq
|
159
|
+
end
|
160
|
+
|
161
|
+
required_fields
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
144
165
|
end
|
data/lib/addressing/enum.rb
CHANGED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Addressing
|
4
|
+
class FieldOverride < Enum
|
5
|
+
HIDDEN = "hidden"
|
6
|
+
OPTIONAL = "optional"
|
7
|
+
REQUIRED = "required"
|
8
|
+
end
|
9
|
+
|
10
|
+
class FieldOverrides
|
11
|
+
attr_reader :hidden_fields, :optional_fields, :required_fields
|
12
|
+
|
13
|
+
def initialize(definition)
|
14
|
+
AddressField.assert_all_exist(definition.keys)
|
15
|
+
FieldOverride.assert_all_exist(definition.values)
|
16
|
+
|
17
|
+
@hidden_fields = []
|
18
|
+
@optional_fields = []
|
19
|
+
@required_fields = []
|
20
|
+
|
21
|
+
definition.each do |field, override|
|
22
|
+
case override
|
23
|
+
when FieldOverride::HIDDEN
|
24
|
+
@hidden_fields << field
|
25
|
+
when FieldOverride::OPTIONAL
|
26
|
+
@optional_fields << field
|
27
|
+
when FieldOverride::REQUIRED
|
28
|
+
@required_fields << field
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Addressing
|
2
4
|
class LazySubdivisions
|
3
5
|
def initialize(parents)
|
@@ -16,6 +18,11 @@ module Addressing
|
|
16
18
|
@subdivisions.any?
|
17
19
|
end
|
18
20
|
|
21
|
+
def empty?
|
22
|
+
do_initialize unless @initialized
|
23
|
+
@subdivisions.empty?
|
24
|
+
end
|
25
|
+
|
19
26
|
private
|
20
27
|
|
21
28
|
def do_initialize
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Addressing
|
4
|
+
module Model
|
5
|
+
def validates_address_format(
|
6
|
+
fields: [:country_code, :administrative_area, :locality, :dependent_locality, :postal_code, :sorting_code, :address_line1, :address_line2, :organization, :given_name, :additional_name, :family_name, :locale], field_overrides: nil, **options)
|
7
|
+
fields = Array(fields)
|
8
|
+
field_overrides ||= FieldOverrides.new({})
|
9
|
+
|
10
|
+
options[:if] ||= -> { fields.any? { |f| changes.key?(f.to_s) } } unless options[:unless]
|
11
|
+
|
12
|
+
class_eval do
|
13
|
+
validate :verify_address_format, **options
|
14
|
+
|
15
|
+
define_method :verify_address_format do
|
16
|
+
values = fields.each_with_object({}) { |f, v| v[f] = send(f) }
|
17
|
+
address = Address.new(**values)
|
18
|
+
|
19
|
+
return unless address.country_code.present?
|
20
|
+
|
21
|
+
address_format = AddressFormat.get(address.country_code)
|
22
|
+
address_format.used_fields
|
23
|
+
|
24
|
+
return unless address.country_code.present?
|
25
|
+
|
26
|
+
address_format = AddressFormat.get(address.country_code)
|
27
|
+
address_format.used_fields
|
28
|
+
|
29
|
+
# Validate the presence of required fields.
|
30
|
+
AddressFormatHelper::required_fields(address_format, field_overrides).each do |required_field|
|
31
|
+
next unless address.send(required_field).blank?
|
32
|
+
|
33
|
+
errors.add(required_field, "should not be blank")
|
34
|
+
end
|
35
|
+
|
36
|
+
used_fields = address_format.used_fields - field_overrides.hidden_fields
|
37
|
+
|
38
|
+
# Validate the absence of unused fields.
|
39
|
+
unused_fields = AddressField.all.values - used_fields
|
40
|
+
unused_fields.each do |unused_field|
|
41
|
+
next if address.send(unused_field).blank?
|
42
|
+
|
43
|
+
errors.add(unused_field, "should be blank")
|
44
|
+
end
|
45
|
+
|
46
|
+
# Validate subdivisions.
|
47
|
+
subdivisions = verify_subdivisions(address, address_format)
|
48
|
+
|
49
|
+
# Validate postal code.
|
50
|
+
verify_postal_code(address.postal_code, subdivisions, address_format) if used_fields.include?(AddressField::POSTAL_CODE)
|
51
|
+
end
|
52
|
+
|
53
|
+
define_method :verify_subdivisions do |address, address_format|
|
54
|
+
# No predefined subdivisions exist, nothing to validate against.
|
55
|
+
return [] if address_format.subdivision_depth < 1
|
56
|
+
|
57
|
+
subdivisions, _parents = address_format.used_subdivision_fields.each_with_index.inject([[], []]) do |(subdivisions, parents), (field, index)|
|
58
|
+
# The field is empty or validation is disabled.
|
59
|
+
# break subdivisions if address.send(field).blank? || address_format.hidden_fields.include?(field)
|
60
|
+
break subdivisions if address.send(field).blank?
|
61
|
+
|
62
|
+
parents << (index > 0 ? address.send(address_format.used_subdivision_fields[index - 1]) : address_format.country_code)
|
63
|
+
subdivision = Subdivision.get(address.send(field), parents)
|
64
|
+
if subdivision.nil?
|
65
|
+
errors.add(field, "should be valid")
|
66
|
+
break [subdivisions, parents]
|
67
|
+
end
|
68
|
+
|
69
|
+
subdivisions << subdivision
|
70
|
+
# No predefined subdivisions below this level, stop here.
|
71
|
+
break [subdivisions, parents] if subdivision.children.empty?
|
72
|
+
|
73
|
+
[subdivisions, parents]
|
74
|
+
end
|
75
|
+
|
76
|
+
subdivisions
|
77
|
+
end
|
78
|
+
|
79
|
+
define_method :verify_postal_code do |postal_code, subdivisions, address_format|
|
80
|
+
# Nothing to validate.
|
81
|
+
return if postal_code.blank?
|
82
|
+
|
83
|
+
full_pattern, start_pattern = subdivisions.inject([address_format.postal_code_pattern, nil]) do |(full_pattern, start_pattern), subdivision|
|
84
|
+
pattern = subdivision.postal_code_pattern
|
85
|
+
next [full_pattern, start_pattern] if pattern.blank?
|
86
|
+
next [pattern, start_pattern] if subdivision.postal_code_pattern_type == PatternType::FULL
|
87
|
+
|
88
|
+
[full_pattern, pattern]
|
89
|
+
end
|
90
|
+
|
91
|
+
if full_pattern
|
92
|
+
# The pattern must match the provided value completely.
|
93
|
+
match = postal_code.match(Regexp.new(full_pattern.gsub("\\\\", "\\").to_s, "i"))
|
94
|
+
if match.nil? || match[0] != postal_code
|
95
|
+
errors.add(AddressField::POSTAL_CODE, "should be valid")
|
96
|
+
return
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
if start_pattern
|
101
|
+
# The pattern must match the start of the provided value.
|
102
|
+
match = postal_code.match(Regexp.new(start_pattern.gsub("\\\\", "\\").to_s, "i"))
|
103
|
+
if match.nil? || postal_code.index(match[0]) != 0
|
104
|
+
errors.add(AddressField::POSTAL_CODE, "should be valid")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/lib/addressing/version.rb
CHANGED
data/lib/addressing.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "cgi"
|
4
|
+
require "digest"
|
4
5
|
require "json"
|
5
6
|
|
7
|
+
require "addressing/enum"
|
6
8
|
require "addressing/address"
|
7
9
|
require "addressing/address_field"
|
8
10
|
require "addressing/address_format"
|
@@ -10,6 +12,7 @@ require "addressing/administrative_area_type"
|
|
10
12
|
require "addressing/country"
|
11
13
|
require "addressing/default_formatter"
|
12
14
|
require "addressing/dependent_locality_type"
|
15
|
+
require "addressing/field_override"
|
13
16
|
require "addressing/lazy_subdivisions"
|
14
17
|
require "addressing/locale"
|
15
18
|
require "addressing/locality_type"
|
@@ -26,3 +29,10 @@ module Addressing
|
|
26
29
|
|
27
30
|
class UnknownLocaleError < Error; end
|
28
31
|
end
|
32
|
+
|
33
|
+
if defined?(ActiveSupport.on_load)
|
34
|
+
ActiveSupport.on_load(:active_record) do
|
35
|
+
require "addressing/model"
|
36
|
+
extend Addressing::Model
|
37
|
+
end
|
38
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: addressing
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robin van der Vleuten
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-04-04 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email: robinvdvleuten@gmail.com
|
@@ -689,9 +689,11 @@ files:
|
|
689
689
|
- lib/addressing/default_formatter.rb
|
690
690
|
- lib/addressing/dependent_locality_type.rb
|
691
691
|
- lib/addressing/enum.rb
|
692
|
+
- lib/addressing/field_override.rb
|
692
693
|
- lib/addressing/lazy_subdivisions.rb
|
693
694
|
- lib/addressing/locale.rb
|
694
695
|
- lib/addressing/locality_type.rb
|
696
|
+
- lib/addressing/model.rb
|
695
697
|
- lib/addressing/pattern_type.rb
|
696
698
|
- lib/addressing/postal_code_type.rb
|
697
699
|
- lib/addressing/postal_label_formatter.rb
|