addressing 2.1.0 → 2.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 +4 -4
- data/CHANGELOG.md +32 -0
- data/README.md +223 -112
- data/data/UPSTREAM_VERSION +1 -0
- data/data/address_formats.json +2256 -206
- data/data/countries.json +1282 -0
- data/data/locale.json +222 -0
- data/data/subdivision/BR-AC.json +22 -22
- data/data/subdivision/BR-AL.json +102 -102
- data/data/subdivision/BR-AM.json +62 -62
- data/data/subdivision/BR-AP.json +16 -16
- data/data/subdivision/BR-BA.json +417 -417
- data/data/subdivision/BR-CE.json +184 -184
- data/data/subdivision/BR-DF.json +1 -1
- data/data/subdivision/BR-ES.json +79 -79
- data/data/subdivision/BR-GO.json +246 -246
- data/data/subdivision/BR-MA.json +217 -217
- data/data/subdivision/BR-MG.json +853 -853
- data/data/subdivision/BR-MS.json +78 -78
- data/data/subdivision/BR-MT.json +141 -141
- data/data/subdivision/BR-PA.json +144 -144
- data/data/subdivision/BR-PB.json +223 -223
- data/data/subdivision/BR-PE.json +185 -185
- data/data/subdivision/BR-PI.json +223 -223
- data/data/subdivision/BR-PR.json +400 -400
- data/data/subdivision/BR-RJ.json +93 -93
- data/data/subdivision/BR-RN.json +166 -166
- data/data/subdivision/BR-RO.json +52 -52
- data/data/subdivision/BR-RR.json +15 -15
- data/data/subdivision/BR-RS.json +497 -497
- data/data/subdivision/BR-SC.json +295 -295
- data/data/subdivision/BR-SE.json +75 -75
- data/data/subdivision/BR-SP.json +645 -645
- data/data/subdivision/BR-TO.json +139 -139
- data/data/subdivision/CL-AI.json +10 -10
- data/data/subdivision/CL-AN.json +9 -9
- data/data/subdivision/CL-AP.json +4 -4
- data/data/subdivision/CL-AR.json +32 -32
- data/data/subdivision/CL-AT.json +9 -9
- data/data/subdivision/CL-BI.json +34 -34
- data/data/subdivision/CL-CO.json +15 -15
- data/data/subdivision/CL-LI.json +33 -33
- data/data/subdivision/CL-LL.json +30 -30
- data/data/subdivision/CL-LR.json +12 -12
- data/data/subdivision/CL-MA.json +11 -11
- data/data/subdivision/CL-ML.json +30 -30
- data/data/subdivision/CL-NB.json +21 -21
- data/data/subdivision/CL-RM.json +52 -52
- data/data/subdivision/CL-TA.json +7 -7
- data/data/subdivision/CL-VS.json +38 -38
- data/data/subdivision/CV.json +9 -9
- data/data/subdivision/KY.json +3 -3
- data/data/subdivision/TV.json +1 -1
- data/lib/addressing/address.rb +7 -3
- data/lib/addressing/address_format.rb +55 -81
- data/lib/addressing/address_validator.rb +98 -0
- data/lib/addressing/blank.rb +32 -0
- data/lib/addressing/country.rb +28 -314
- data/lib/addressing/data_source.rb +75 -0
- data/lib/addressing/default_formatter.rb +25 -25
- data/lib/addressing/enum.rb +2 -6
- data/lib/addressing/field_violation.rb +17 -0
- data/lib/addressing/lazy_subdivisions.rb +16 -15
- data/lib/addressing/locale.rb +20 -269
- data/lib/addressing/model.rb +20 -78
- data/lib/addressing/postal_label_formatter.rb +16 -15
- data/lib/addressing/subdivision.rb +113 -116
- data/lib/addressing/version.rb +1 -1
- data/lib/addressing.rb +6 -1
- metadata +9 -3
- data/data/address_formats.dump +0 -0
|
@@ -1,33 +1,34 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Addressing
|
|
4
|
+
# The children of a subdivision, loaded on first access.
|
|
5
|
+
#
|
|
6
|
+
# Behaves like the hash that Subdivision.all returns for the same parents.
|
|
4
7
|
class LazySubdivisions
|
|
8
|
+
extend Forwardable
|
|
9
|
+
include Enumerable
|
|
10
|
+
|
|
11
|
+
def_delegators :subdivisions, :each, :size, :keys, :values, :key?, :include?, :member?, :[], :fetch, :select, :filter, :reject
|
|
12
|
+
|
|
5
13
|
def initialize(parents)
|
|
6
14
|
@parents = parents
|
|
7
|
-
@subdivisions = {}
|
|
8
|
-
@initialized = false
|
|
9
15
|
end
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@subdivisions
|
|
17
|
+
# Checks for children without building them.
|
|
18
|
+
def empty?
|
|
19
|
+
@subdivisions ? @subdivisions.empty? : !Subdivision.any?(@parents)
|
|
14
20
|
end
|
|
15
21
|
|
|
16
|
-
def any?
|
|
17
|
-
|
|
18
|
-
@subdivisions.any?
|
|
19
|
-
end
|
|
22
|
+
def any?(*args, &block)
|
|
23
|
+
return subdivisions.any?(*args, &block) if block || args.any?
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
do_initialize unless @initialized
|
|
23
|
-
@subdivisions.empty?
|
|
25
|
+
!empty?
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
private
|
|
27
29
|
|
|
28
|
-
def
|
|
29
|
-
@
|
|
30
|
-
@subdivisions = Subdivision.all(@parents)
|
|
30
|
+
def subdivisions
|
|
31
|
+
@subdivisions ||= Subdivision.all(@parents)
|
|
31
32
|
end
|
|
32
33
|
end
|
|
33
34
|
end
|
data/lib/addressing/locale.rb
CHANGED
|
@@ -2,228 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
module Addressing
|
|
4
4
|
class Locale
|
|
5
|
-
ALIASES = {
|
|
6
|
-
"az-AZ" => "az-Latn-AZ",
|
|
7
|
-
"bs-BA" => "bs-Latn-BA",
|
|
8
|
-
"ha-GH" => "ha-Latn-GH",
|
|
9
|
-
"ha-NE" => "ha-Latn-NE",
|
|
10
|
-
"ha-NG" => "ha-Latn-NG",
|
|
11
|
-
"in" => "id",
|
|
12
|
-
"in-ID" => "id-ID",
|
|
13
|
-
"iw" => "he",
|
|
14
|
-
"iw-IL" => "he-IL",
|
|
15
|
-
"kk-KZ" => "kk-Cyrl-KZ",
|
|
16
|
-
"ks-IN" => "ks-Arab-IN",
|
|
17
|
-
"ky-KG" => "ky-Cyrl-KG",
|
|
18
|
-
"mn-MN" => "mn-Cyrl-MN",
|
|
19
|
-
"mo" => "ro-MD",
|
|
20
|
-
"ms-BN" => "ms-Latn-BN",
|
|
21
|
-
"ms-MY" => "ms-Latn-MY",
|
|
22
|
-
"ms-SG" => "ms-Latn-SG",
|
|
23
|
-
"no" => "nb",
|
|
24
|
-
"no-NO" => "nb-NO",
|
|
25
|
-
"no-NO-NY" => "nn-NO",
|
|
26
|
-
"pa-IN" => "pa-Guru-IN",
|
|
27
|
-
"pa-PK" => "pa-Arab-PK",
|
|
28
|
-
"sh" => "sr-Latn",
|
|
29
|
-
"sh-BA" => "sr-Latn-BA",
|
|
30
|
-
"sh-CS" => "sr-Latn-RS",
|
|
31
|
-
"sh-YU" => "sr-Latn-RS",
|
|
32
|
-
"shi-MA" => "shi-Tfng-MA",
|
|
33
|
-
"sr-BA" => "sr-Cyrl-BA",
|
|
34
|
-
"sr-ME" => "sr-Latn-ME",
|
|
35
|
-
"sr-RS" => "sr-Cyrl-RS",
|
|
36
|
-
"sr-XK" => "sr-Cyrl-XK",
|
|
37
|
-
"tl" => "fil",
|
|
38
|
-
"tl-PH" => "fil-PH",
|
|
39
|
-
"tzm-MA" => "tzm-Latn-MA",
|
|
40
|
-
"ug-CN" => "ug-Arab-CN",
|
|
41
|
-
"uz-AF" => "uz-Arab-AF",
|
|
42
|
-
"uz-UZ" => "uz-Latn-UZ",
|
|
43
|
-
"vai-LR" => "vai-Vaii-LR",
|
|
44
|
-
"zh-CN" => "zh-Hans-CN",
|
|
45
|
-
"zh-HK" => "zh-Hant-HK",
|
|
46
|
-
"zh-MO" => "zh-Hant-MO",
|
|
47
|
-
"zh-SG" => "zh-Hans-SG",
|
|
48
|
-
"zh-TW" => "zh-Hant-TW"
|
|
49
|
-
}.freeze
|
|
50
|
-
|
|
51
|
-
PARENTS = {
|
|
52
|
-
"en-150" => "en-001",
|
|
53
|
-
"en-AG" => "en-001",
|
|
54
|
-
"en-AI" => "en-001",
|
|
55
|
-
"en-AT" => "en-150",
|
|
56
|
-
"en-AU" => "en-001",
|
|
57
|
-
"en-BB" => "en-001",
|
|
58
|
-
"en-BE" => "en-150",
|
|
59
|
-
"en-BM" => "en-001",
|
|
60
|
-
"en-BS" => "en-001",
|
|
61
|
-
"en-BW" => "en-001",
|
|
62
|
-
"en-BZ" => "en-001",
|
|
63
|
-
"en-CC" => "en-001",
|
|
64
|
-
"en-CH" => "en-150",
|
|
65
|
-
"en-CK" => "en-001",
|
|
66
|
-
"en-CM" => "en-001",
|
|
67
|
-
"en-CX" => "en-001",
|
|
68
|
-
"en-CY" => "en-001",
|
|
69
|
-
"en-CZ" => "en-150",
|
|
70
|
-
"en-DE" => "en-150",
|
|
71
|
-
"en-DG" => "en-001",
|
|
72
|
-
"en-DK" => "en-150",
|
|
73
|
-
"en-DM" => "en-001",
|
|
74
|
-
"en-ER" => "en-001",
|
|
75
|
-
"en-ES" => "en-150",
|
|
76
|
-
"en-FI" => "en-150",
|
|
77
|
-
"en-FJ" => "en-001",
|
|
78
|
-
"en-FK" => "en-001",
|
|
79
|
-
"en-FM" => "en-001",
|
|
80
|
-
"en-FR" => "en-150",
|
|
81
|
-
"en-GB" => "en-001",
|
|
82
|
-
"en-GD" => "en-001",
|
|
83
|
-
"en-GG" => "en-001",
|
|
84
|
-
"en-GH" => "en-001",
|
|
85
|
-
"en-GI" => "en-001",
|
|
86
|
-
"en-GM" => "en-001",
|
|
87
|
-
"en-GS" => "en-001",
|
|
88
|
-
"en-GY" => "en-001",
|
|
89
|
-
"en-HK" => "en-001",
|
|
90
|
-
"en-HU" => "en-150",
|
|
91
|
-
"en-ID" => "en-001",
|
|
92
|
-
"en-IE" => "en-001",
|
|
93
|
-
"en-IL" => "en-001",
|
|
94
|
-
"en-IM" => "en-001",
|
|
95
|
-
"en-IN" => "en-001",
|
|
96
|
-
"en-IO" => "en-001",
|
|
97
|
-
"en-IT" => "en-150",
|
|
98
|
-
"en-JE" => "en-001",
|
|
99
|
-
"en-JM" => "en-001",
|
|
100
|
-
"en-KE" => "en-001",
|
|
101
|
-
"en-KI" => "en-001",
|
|
102
|
-
"en-KN" => "en-001",
|
|
103
|
-
"en-KY" => "en-001",
|
|
104
|
-
"en-LC" => "en-001",
|
|
105
|
-
"en-LR" => "en-001",
|
|
106
|
-
"en-LS" => "en-001",
|
|
107
|
-
"en-MG" => "en-001",
|
|
108
|
-
"en-MO" => "en-001",
|
|
109
|
-
"en-MS" => "en-001",
|
|
110
|
-
"en-MT" => "en-001",
|
|
111
|
-
"en-MU" => "en-001",
|
|
112
|
-
"en-MV" => "en-001",
|
|
113
|
-
"en-MW" => "en-001",
|
|
114
|
-
"en-MY" => "en-001",
|
|
115
|
-
"en-NA" => "en-001",
|
|
116
|
-
"en-NF" => "en-001",
|
|
117
|
-
"en-NG" => "en-001",
|
|
118
|
-
"en-NL" => "en-150",
|
|
119
|
-
"en-NO" => "en-150",
|
|
120
|
-
"en-NR" => "en-001",
|
|
121
|
-
"en-NU" => "en-001",
|
|
122
|
-
"en-NZ" => "en-001",
|
|
123
|
-
"en-PG" => "en-001",
|
|
124
|
-
"en-PK" => "en-001",
|
|
125
|
-
"en-PL" => "en-150",
|
|
126
|
-
"en-PN" => "en-001",
|
|
127
|
-
"en-PT" => "en-150",
|
|
128
|
-
"en-PW" => "en-001",
|
|
129
|
-
"en-RO" => "en-150",
|
|
130
|
-
"en-RW" => "en-001",
|
|
131
|
-
"en-SB" => "en-001",
|
|
132
|
-
"en-SC" => "en-001",
|
|
133
|
-
"en-SD" => "en-001",
|
|
134
|
-
"en-SE" => "en-150",
|
|
135
|
-
"en-SG" => "en-001",
|
|
136
|
-
"en-SH" => "en-001",
|
|
137
|
-
"en-SI" => "en-150",
|
|
138
|
-
"en-SK" => "en-150",
|
|
139
|
-
"en-SL" => "en-001",
|
|
140
|
-
"en-SS" => "en-001",
|
|
141
|
-
"en-SX" => "en-001",
|
|
142
|
-
"en-SZ" => "en-001",
|
|
143
|
-
"en-TC" => "en-001",
|
|
144
|
-
"en-TK" => "en-001",
|
|
145
|
-
"en-TO" => "en-001",
|
|
146
|
-
"en-TT" => "en-001",
|
|
147
|
-
"en-TV" => "en-001",
|
|
148
|
-
"en-TZ" => "en-001",
|
|
149
|
-
"en-UG" => "en-001",
|
|
150
|
-
"en-VC" => "en-001",
|
|
151
|
-
"en-VG" => "en-001",
|
|
152
|
-
"en-VU" => "en-001",
|
|
153
|
-
"en-WS" => "en-001",
|
|
154
|
-
"en-ZA" => "en-001",
|
|
155
|
-
"en-ZM" => "en-001",
|
|
156
|
-
"en-ZW" => "en-001",
|
|
157
|
-
"es-AR" => "es-419",
|
|
158
|
-
"es-BO" => "es-419",
|
|
159
|
-
"es-BR" => "es-419",
|
|
160
|
-
"es-BZ" => "es-419",
|
|
161
|
-
"es-CL" => "es-419",
|
|
162
|
-
"es-CO" => "es-419",
|
|
163
|
-
"es-CR" => "es-419",
|
|
164
|
-
"es-CU" => "es-419",
|
|
165
|
-
"es-DO" => "es-419",
|
|
166
|
-
"es-EC" => "es-419",
|
|
167
|
-
"es-GT" => "es-419",
|
|
168
|
-
"es-HN" => "es-419",
|
|
169
|
-
"es-MX" => "es-419",
|
|
170
|
-
"es-NI" => "es-419",
|
|
171
|
-
"es-PA" => "es-419",
|
|
172
|
-
"es-PE" => "es-419",
|
|
173
|
-
"es-PR" => "es-419",
|
|
174
|
-
"es-PY" => "es-419",
|
|
175
|
-
"es-SV" => "es-419",
|
|
176
|
-
"es-US" => "es-419",
|
|
177
|
-
"es-UY" => "es-419",
|
|
178
|
-
"es-VE" => "es-419",
|
|
179
|
-
"hi-Latn" => "und",
|
|
180
|
-
"ht" => "fr-HT",
|
|
181
|
-
"nb" => "no",
|
|
182
|
-
"nn" => "no",
|
|
183
|
-
"pt-AO" => "pt-PT",
|
|
184
|
-
"pt-CH" => "pt-PT",
|
|
185
|
-
"pt-CV" => "pt-PT",
|
|
186
|
-
"pt-GQ" => "pt-PT",
|
|
187
|
-
"pt-GW" => "pt-PT",
|
|
188
|
-
"pt-LU" => "pt-PT",
|
|
189
|
-
"pt-MO" => "pt-PT",
|
|
190
|
-
"pt-MZ" => "pt-PT",
|
|
191
|
-
"pt-ST" => "pt-PT",
|
|
192
|
-
"pt-TL" => "pt-PT",
|
|
193
|
-
"az-Arab" => "und",
|
|
194
|
-
"az-Cyrl" => "und",
|
|
195
|
-
"bal-Latn" => "und",
|
|
196
|
-
"blt-Latn" => "und",
|
|
197
|
-
"bs-Cyrl" => "und",
|
|
198
|
-
"byn-Latn" => "und",
|
|
199
|
-
"en-Dsrt" => "und",
|
|
200
|
-
"en-Shaw" => "und",
|
|
201
|
-
"iu-Latn" => "und",
|
|
202
|
-
"kk-Arab" => "und",
|
|
203
|
-
"ks-Deva" => "und",
|
|
204
|
-
"ku-Arab" => "und",
|
|
205
|
-
"ky-Arab" => "und",
|
|
206
|
-
"ky-Latn" => "und",
|
|
207
|
-
"ml-Arab" => "und",
|
|
208
|
-
"mn-Mong" => "und",
|
|
209
|
-
"mni-Mtei" => "und",
|
|
210
|
-
"ms-Arab" => "und",
|
|
211
|
-
"pa-Arab" => "und",
|
|
212
|
-
"sat-Deva" => "und",
|
|
213
|
-
"sd-Deva" => "und",
|
|
214
|
-
"sd-Khoj" => "und",
|
|
215
|
-
"sd-Sind" => "und",
|
|
216
|
-
"so-Arab" => "und",
|
|
217
|
-
"sr-Latn" => "und",
|
|
218
|
-
"sw-Arab" => "und",
|
|
219
|
-
"tg-Arab" => "und",
|
|
220
|
-
"uz-Arab" => "und",
|
|
221
|
-
"uz-Cyrl" => "und",
|
|
222
|
-
"yue-Hans" => "und",
|
|
223
|
-
"zh-Hant" => "und",
|
|
224
|
-
"zh-Hant-MO" => "zh-Hant-HK"
|
|
225
|
-
}.freeze
|
|
226
|
-
|
|
227
5
|
# Checks whether two locales match.
|
|
228
6
|
def self.match(first_locale, second_locale)
|
|
229
7
|
return false if first_locale.to_s.empty? || second_locale.to_s.empty?
|
|
@@ -238,12 +16,7 @@ module Addressing
|
|
|
238
16
|
def self.match_candidates(first_locale, second_locale)
|
|
239
17
|
return false if first_locale.to_s.empty? || second_locale.to_s.empty?
|
|
240
18
|
|
|
241
|
-
first_locale
|
|
242
|
-
second_locale = canonicalize(second_locale)
|
|
243
|
-
first_locale_candidates = candidates(first_locale)
|
|
244
|
-
second_locale_candidates = candidates(second_locale)
|
|
245
|
-
|
|
246
|
-
(first_locale_candidates & second_locale_candidates).any?
|
|
19
|
+
(candidates(canonicalize(first_locale)) & candidates(canonicalize(second_locale))).any?
|
|
247
20
|
end
|
|
248
21
|
|
|
249
22
|
# Resolves the locale from the available locales.
|
|
@@ -254,14 +27,7 @@ module Addressing
|
|
|
254
27
|
# If no candidate is found in the list, an error is raised.
|
|
255
28
|
def self.resolve(available_locales, locale, fallback_locale = nil)
|
|
256
29
|
locale = canonicalize(locale)
|
|
257
|
-
resolved_locale =
|
|
258
|
-
|
|
259
|
-
candidates(locale, fallback_locale).each do |candidate|
|
|
260
|
-
if available_locales.include?(candidate)
|
|
261
|
-
resolved_locale = candidate
|
|
262
|
-
break
|
|
263
|
-
end
|
|
264
|
-
end
|
|
30
|
+
resolved_locale = candidates(locale, fallback_locale).find { |candidate| available_locales.include?(candidate) }
|
|
265
31
|
|
|
266
32
|
# No locale could be resolved, stop here.
|
|
267
33
|
raise UnknownLocaleError.new(locale) if resolved_locale.nil?
|
|
@@ -304,54 +70,39 @@ module Addressing
|
|
|
304
70
|
# 1) sr-Latn
|
|
305
71
|
# 2) sr
|
|
306
72
|
def self.candidates(locale, fallback_locale = nil)
|
|
307
|
-
|
|
308
|
-
candidates
|
|
309
|
-
|
|
310
|
-
while (parent = parent(locale))
|
|
311
|
-
candidates << parent
|
|
312
|
-
locale = parent
|
|
313
|
-
end
|
|
314
|
-
|
|
315
|
-
if fallback_locale
|
|
316
|
-
candidates << fallback_locale
|
|
317
|
-
|
|
318
|
-
while (parent = parent(fallback_locale))
|
|
319
|
-
candidates << parent
|
|
320
|
-
fallback_locale = parent
|
|
321
|
-
end
|
|
322
|
-
end
|
|
323
|
-
|
|
73
|
+
candidates = lineage(replace_alias(locale))
|
|
74
|
+
candidates += lineage(fallback_locale) if fallback_locale
|
|
324
75
|
candidates.uniq
|
|
325
76
|
end
|
|
326
77
|
|
|
78
|
+
# Gets the given locale followed by its parents.
|
|
79
|
+
def self.lineage(locale)
|
|
80
|
+
lineage = [locale]
|
|
81
|
+
lineage << locale while (locale = parent(locale))
|
|
82
|
+
lineage
|
|
83
|
+
end
|
|
84
|
+
private_class_method :lineage
|
|
85
|
+
|
|
327
86
|
# Gets the parent for the given locale.
|
|
328
87
|
def self.parent(locale)
|
|
329
|
-
parent = nil
|
|
330
|
-
|
|
331
|
-
if PARENTS.key?(locale)
|
|
332
|
-
parent = PARENTS[locale]
|
|
333
|
-
elsif locale.include?("-")
|
|
334
|
-
locale_parts = locale.split("-")
|
|
335
|
-
locale_parts.pop
|
|
336
|
-
parent = locale_parts.join("-")
|
|
337
|
-
end
|
|
88
|
+
parent = definitions["parents"].fetch(locale) { locale.include?("-") ? locale.rpartition("-").first : nil }
|
|
338
89
|
|
|
339
90
|
# The library doesn't have data for the empty `und` locale, it
|
|
340
91
|
# is more user friendly to use the configured fallback instead.
|
|
341
|
-
|
|
342
|
-
parent = nil
|
|
343
|
-
end
|
|
344
|
-
|
|
345
|
-
parent
|
|
92
|
+
(parent == "und") ? nil : parent
|
|
346
93
|
end
|
|
347
94
|
|
|
348
95
|
# Replaces a locale alias with the real locale.
|
|
349
96
|
#
|
|
350
97
|
# For example, "zh-CN" is replaced with "zh-Hans-CN".
|
|
351
98
|
def self.replace_alias(locale)
|
|
352
|
-
|
|
99
|
+
definitions["aliases"].fetch(locale, locale)
|
|
100
|
+
end
|
|
353
101
|
|
|
354
|
-
|
|
102
|
+
# Gets the locale aliases and parents.
|
|
103
|
+
def self.definitions
|
|
104
|
+
Addressing.data_source.fetch("locale")
|
|
355
105
|
end
|
|
106
|
+
private_class_method :definitions
|
|
356
107
|
end
|
|
357
108
|
end
|
data/lib/addressing/model.rb
CHANGED
|
@@ -2,94 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
module Addressing
|
|
4
4
|
module Model
|
|
5
|
+
# The error message for each kind of field violation.
|
|
6
|
+
FIELD_VIOLATION_MESSAGES = {
|
|
7
|
+
blank: "should not be blank",
|
|
8
|
+
present: "should be blank",
|
|
9
|
+
invalid: "should be valid"
|
|
10
|
+
}.freeze
|
|
11
|
+
private_constant :FIELD_VIOLATION_MESSAGES
|
|
12
|
+
|
|
5
13
|
def validates_address_format(
|
|
6
|
-
fields:
|
|
14
|
+
fields: Address::FIELDS, field_overrides: nil, verify_postal_code: true, **options
|
|
7
15
|
)
|
|
8
16
|
fields = Array(fields)
|
|
9
17
|
field_overrides ||= FieldOverrides.new({})
|
|
10
18
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class_eval do
|
|
14
|
-
validate :verify_address_format, **options
|
|
15
|
-
|
|
16
|
-
define_method :verify_address_format do
|
|
17
|
-
values = fields.each_with_object({}) { |f, v| v[f] = send(f) if respond_to?(f) }
|
|
18
|
-
address = Address.new(**values)
|
|
19
|
-
|
|
20
|
-
return unless address.country_code.present?
|
|
21
|
-
|
|
22
|
-
address_format = AddressFormat.get(address.country_code)
|
|
23
|
-
|
|
24
|
-
# Validate the presence of required fields.
|
|
25
|
-
AddressFormatHelper.required_fields(address_format, field_overrides).each do |required_field|
|
|
26
|
-
next unless address.send(required_field).blank?
|
|
27
|
-
|
|
28
|
-
errors.add(required_field, "should not be blank")
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
used_fields = address_format.used_fields - field_overrides.hidden_fields
|
|
32
|
-
|
|
33
|
-
# Validate the absence of unused fields.
|
|
34
|
-
unused_fields = AddressField.all.values - used_fields
|
|
35
|
-
unused_fields.each do |unused_field|
|
|
36
|
-
next if address.send(unused_field).blank?
|
|
37
|
-
|
|
38
|
-
errors.add(unused_field, "should be blank")
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Validate subdivisions.
|
|
42
|
-
subdivisions = verify_subdivisions(address, address_format, field_overrides)
|
|
43
|
-
|
|
44
|
-
# Validate postal code.
|
|
45
|
-
verify_postal_code(address.postal_code, subdivisions, address_format) if used_fields.include?(AddressField::POSTAL_CODE) && verify_postal_code
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
define_method :verify_subdivisions do |address, address_format, field_overrides|
|
|
49
|
-
parents = [address_format.country_code]
|
|
50
|
-
subdivisions = []
|
|
51
|
-
|
|
52
|
-
address_format.subdivision_fields.each do |field|
|
|
53
|
-
value = address.send(field)
|
|
54
|
-
# The field is empty or validation is disabled.
|
|
55
|
-
break if value.blank? || field_overrides.hidden_fields.include?(field)
|
|
56
|
-
|
|
57
|
-
subdivision = Subdivision.get(value, parents)
|
|
58
|
-
if subdivision.nil?
|
|
59
|
-
errors.add(field, "should be valid")
|
|
60
|
-
break
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
parents << value
|
|
64
|
-
subdivisions << subdivision
|
|
65
|
-
# No predefined subdivisions below this level, stop here.
|
|
66
|
-
break unless subdivision.children?
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
subdivisions
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
define_method :verify_postal_code do |postal_code, subdivisions, address_format|
|
|
73
|
-
# Nothing to validate.
|
|
74
|
-
return if postal_code.blank?
|
|
19
|
+
# Without change tracking, as on a plain ActiveModel class, validate every time.
|
|
20
|
+
options[:if] ||= -> { !respond_to?(:changes) || fields.any? { |f| changes.key?(f.to_s) } } unless options[:unless]
|
|
75
21
|
|
|
76
|
-
|
|
77
|
-
subdivision_pattern = subdivision.postal_code_pattern
|
|
78
|
-
next pattern if subdivision_pattern.blank?
|
|
22
|
+
validate :validate_address_format, **options
|
|
79
23
|
|
|
80
|
-
|
|
81
|
-
|
|
24
|
+
# A method rather than a block, so that a later call, in a subclass for
|
|
25
|
+
# example, replaces the configuration of an earlier one.
|
|
26
|
+
define_method :validate_address_format do
|
|
27
|
+
values = fields.each_with_object({}) { |f, v| v[f] = send(f) if respond_to?(f) }
|
|
28
|
+
address = Address.new(**values)
|
|
82
29
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
match = postal_code.match(Regexp.new(pattern.gsub("\\\\", "\\").to_s, "i"))
|
|
86
|
-
if match.nil? || match[0] != postal_code
|
|
87
|
-
errors.add(AddressField::POSTAL_CODE, "should be valid")
|
|
88
|
-
nil
|
|
89
|
-
end
|
|
90
|
-
end
|
|
30
|
+
AddressValidator.validate(address, field_overrides: field_overrides, verify_postal_code: verify_postal_code).each do |violation|
|
|
31
|
+
errors.add(violation.field, violation.kind, message: FIELD_VIOLATION_MESSAGES.fetch(violation.kind))
|
|
91
32
|
end
|
|
92
33
|
end
|
|
34
|
+
private :validate_address_format
|
|
93
35
|
end
|
|
94
36
|
end
|
|
95
37
|
end
|
|
@@ -2,28 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
module Addressing
|
|
4
4
|
class PostalLabelFormatter < DefaultFormatter
|
|
5
|
-
DEFAULT_OPTIONS =
|
|
6
|
-
locale: DEFAULT_LOCALE,
|
|
7
|
-
html: false,
|
|
8
|
-
html_tag: "p",
|
|
9
|
-
html_attributes: {translate: "no"},
|
|
10
|
-
origin_country: ""
|
|
11
|
-
}
|
|
5
|
+
DEFAULT_OPTIONS = DefaultFormatter::DEFAULT_OPTIONS.merge(html: false, origin_country: "")
|
|
12
6
|
|
|
13
7
|
protected
|
|
14
8
|
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
# The origin country has no default, so format raises until it is given,
|
|
10
|
+
# to the constructor or to format.
|
|
11
|
+
def assert_options(options)
|
|
12
|
+
super
|
|
13
|
+
|
|
14
|
+
return unless options.key?(:origin_country)
|
|
15
|
+
|
|
16
|
+
origin_country = options[:origin_country]
|
|
17
|
+
raise ArgumentError, "The option `origin_country` must be a non-empty string." unless origin_country.is_a?(String) && !origin_country.empty?
|
|
18
|
+
end
|
|
17
19
|
|
|
20
|
+
def build_view(address, address_format, options)
|
|
18
21
|
view = super
|
|
19
|
-
view
|
|
22
|
+
view.each do |key, element|
|
|
20
23
|
# Uppercase fields where required by the format.
|
|
21
24
|
element[:value] = element[:value].upcase if address_format.uppercase_fields.include?(key)
|
|
22
|
-
|
|
23
|
-
end.to_h
|
|
25
|
+
end
|
|
24
26
|
|
|
25
27
|
# Handle international mailing.
|
|
26
|
-
if address.country_code != options[:origin_country]
|
|
28
|
+
if address.country_code != options[:origin_country].upcase
|
|
27
29
|
# Prefix the postal code.
|
|
28
30
|
field = AddressField::POSTAL_CODE
|
|
29
31
|
|
|
@@ -37,8 +39,7 @@ module Addressing
|
|
|
37
39
|
# transit, it is desirable for the name of the country of
|
|
38
40
|
# destination to be added in an internationally known language.
|
|
39
41
|
country = view["country"][:value]
|
|
40
|
-
|
|
41
|
-
english_country = english_countries[address.country_code]
|
|
42
|
+
english_country = country_list("en")[address.country_code]
|
|
42
43
|
|
|
43
44
|
if country != english_country
|
|
44
45
|
country += " - #{english_country}"
|