addressing 2.0.1 → 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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/README.md +223 -111
  4. data/data/UPSTREAM_VERSION +1 -0
  5. data/data/address_formats.json +2256 -206
  6. data/data/countries.json +1282 -0
  7. data/data/locale.json +222 -0
  8. data/data/subdivision/AE.json +1 -1
  9. data/data/subdivision/BR-AC.json +22 -22
  10. data/data/subdivision/BR-AL.json +102 -102
  11. data/data/subdivision/BR-AM.json +62 -62
  12. data/data/subdivision/BR-AP.json +16 -16
  13. data/data/subdivision/BR-BA.json +417 -417
  14. data/data/subdivision/BR-CE.json +184 -184
  15. data/data/subdivision/BR-DF.json +1 -1
  16. data/data/subdivision/BR-ES.json +79 -79
  17. data/data/subdivision/BR-GO.json +246 -246
  18. data/data/subdivision/BR-MA.json +217 -217
  19. data/data/subdivision/BR-MG.json +853 -853
  20. data/data/subdivision/BR-MS.json +78 -78
  21. data/data/subdivision/BR-MT.json +141 -141
  22. data/data/subdivision/BR-PA.json +144 -144
  23. data/data/subdivision/BR-PB.json +223 -223
  24. data/data/subdivision/BR-PE.json +185 -185
  25. data/data/subdivision/BR-PI.json +223 -223
  26. data/data/subdivision/BR-PR.json +400 -400
  27. data/data/subdivision/BR-RJ.json +93 -93
  28. data/data/subdivision/BR-RN.json +166 -166
  29. data/data/subdivision/BR-RO.json +52 -52
  30. data/data/subdivision/BR-RR.json +15 -15
  31. data/data/subdivision/BR-RS.json +497 -497
  32. data/data/subdivision/BR-SC.json +295 -295
  33. data/data/subdivision/BR-SE.json +75 -75
  34. data/data/subdivision/BR-SP.json +645 -645
  35. data/data/subdivision/BR-TO.json +139 -139
  36. data/data/subdivision/CA.json +6 -12
  37. data/data/subdivision/CL-AI.json +10 -10
  38. data/data/subdivision/CL-AN.json +9 -9
  39. data/data/subdivision/CL-AP.json +4 -4
  40. data/data/subdivision/CL-AR.json +32 -32
  41. data/data/subdivision/CL-AT.json +9 -9
  42. data/data/subdivision/CL-BI.json +34 -34
  43. data/data/subdivision/CL-CO.json +15 -15
  44. data/data/subdivision/CL-LI.json +33 -33
  45. data/data/subdivision/CL-LL.json +30 -30
  46. data/data/subdivision/CL-LR.json +12 -12
  47. data/data/subdivision/CL-MA.json +11 -11
  48. data/data/subdivision/CL-ML.json +30 -30
  49. data/data/subdivision/CL-NB.json +21 -21
  50. data/data/subdivision/CL-RM.json +52 -52
  51. data/data/subdivision/CL-TA.json +7 -7
  52. data/data/subdivision/CL-VS.json +38 -38
  53. data/data/subdivision/CO.json +1 -1
  54. data/data/subdivision/CV.json +9 -9
  55. data/data/subdivision/GB.json +675 -0
  56. data/data/subdivision/GT.json +71 -0
  57. data/data/subdivision/IT.json +13 -1
  58. data/data/subdivision/KY.json +3 -3
  59. data/data/subdivision/NG.json +1 -1
  60. data/data/subdivision/RU.json +4 -4
  61. data/data/subdivision/TH.json +7 -7
  62. data/data/subdivision/TR.json +1 -1
  63. data/data/subdivision/TV.json +1 -1
  64. data/data/subdivision/VE.json +4 -4
  65. data/data/subdivision/VN.json +1 -1
  66. data/lib/addressing/address.rb +7 -3
  67. data/lib/addressing/address_format.rb +93 -97
  68. data/lib/addressing/address_validator.rb +98 -0
  69. data/lib/addressing/blank.rb +32 -0
  70. data/lib/addressing/country.rb +28 -314
  71. data/lib/addressing/data_source.rb +75 -0
  72. data/lib/addressing/default_formatter.rb +61 -33
  73. data/lib/addressing/enum.rb +2 -6
  74. data/lib/addressing/field_violation.rb +17 -0
  75. data/lib/addressing/lazy_subdivisions.rb +16 -15
  76. data/lib/addressing/locale.rb +20 -269
  77. data/lib/addressing/model.rb +20 -79
  78. data/lib/addressing/postal_label_formatter.rb +16 -15
  79. data/lib/addressing/subdivision.rb +121 -106
  80. data/lib/addressing/version.rb +1 -1
  81. data/lib/addressing.rb +6 -1
  82. metadata +11 -3
  83. data/data/address_formats.dump +0 -0
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Addressing
4
+ # One field of an address that breaks a rule of the address format for its
5
+ # country, together with the kind of rule it breaks. A blank country code is
6
+ # also a field violation.
7
+ #
8
+ # field is the field as a symbol, such as :postal_code. kind is one of:
9
+ #
10
+ # - :blank, the field must not be blank.
11
+ # - :present, the field must be blank.
12
+ # - :invalid, the value matches no predefined subdivision or does not match
13
+ # the postal code pattern.
14
+ #
15
+ # The kinds match the error types of Rails.
16
+ FieldViolation = Data.define(:field, :kind)
17
+ end
@@ -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
- def [](key)
12
- do_initialize unless @initialized
13
- @subdivisions[key]
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
- do_initialize unless @initialized
18
- @subdivisions.any?
19
- end
22
+ def any?(*args, &block)
23
+ return subdivisions.any?(*args, &block) if block || args.any?
20
24
 
21
- def empty?
22
- do_initialize unless @initialized
23
- @subdivisions.empty?
25
+ !empty?
24
26
  end
25
27
 
26
28
  private
27
29
 
28
- def do_initialize
29
- @initialized = true
30
- @subdivisions = Subdivision.all(@parents)
30
+ def subdivisions
31
+ @subdivisions ||= Subdivision.all(@parents)
31
32
  end
32
33
  end
33
34
  end
@@ -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 = canonicalize(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 = nil
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
- locale = replace_alias(locale)
308
- candidates = [locale]
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
- if parent == "und"
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
- return locale if locale.to_s.empty? || !ALIASES.key?(locale)
99
+ definitions["aliases"].fetch(locale, locale)
100
+ end
353
101
 
354
- ALIASES[locale]
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
@@ -2,95 +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: [: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, verify_postal_code: true, **options
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
- options[:if] ||= -> { fields.any? { |f| changes.key?(f.to_s) } } unless options[:unless]
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
- # No predefined subdivisions exist, nothing to validate against.
50
- return [] if address_format.subdivision_depth < 1
51
-
52
- subdivisions, _parents = address_format.used_subdivision_fields.each_with_index.inject([[], []]) do |(subdivisions, parents), (field, index)|
53
- # The field is empty or validation is disabled.
54
- break [subdivisions, parents] if address.send(field).blank? || field_overrides.hidden_fields.include?(field)
55
-
56
- parents << ((index > 0) ? address.send(address_format.used_subdivision_fields[index - 1]) : address_format.country_code)
57
- subdivision = Subdivision.get(address.send(field), parents)
58
- if subdivision.nil?
59
- errors.add(field, "should be valid")
60
- break [subdivisions, parents]
61
- end
62
-
63
- subdivisions << subdivision
64
- # No predefined subdivisions below this level, stop here.
65
- break [subdivisions, parents] if subdivision.children.empty?
66
-
67
- [subdivisions, parents]
68
- end
69
-
70
- subdivisions
71
- end
72
-
73
- define_method :verify_postal_code do |postal_code, subdivisions, address_format|
74
- # Nothing to validate.
75
- 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]
76
21
 
77
- pattern = subdivisions.inject(address_format.postal_code_pattern) do |pattern, subdivision|
78
- subdivision_pattern = subdivision.postal_code_pattern
79
- next pattern if subdivision_pattern.blank?
22
+ validate :validate_address_format, **options
80
23
 
81
- subdivision_pattern
82
- end
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)
83
29
 
84
- if pattern
85
- # The pattern must match the provided value completely.
86
- match = postal_code.match(Regexp.new(pattern.gsub("\\\\", "\\").to_s, "i"))
87
- if match.nil? || match[0] != postal_code
88
- errors.add(AddressField::POSTAL_CODE, "should be valid")
89
- nil
90
- end
91
- 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))
92
32
  end
93
33
  end
34
+ private :validate_address_format
94
35
  end
95
36
  end
96
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
- def build_view(address, address_format, options)
16
- raise ArgumentError, "The origin_country option cannot be empty." if options[:origin_country].empty?
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 = view.map do |key, element|
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
- [key, element]
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
- english_countries = Country.list("en")
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}"