addressing 0.7.0 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f841d5e2269026b18c8731756d71c3cd03d1b5f25bed54cf6fc8e0c0351770e1
4
- data.tar.gz: befc373ee2e5370acf8b1f35a9cfa2f171bb78f99a17acac92dc3ae2841cb7e6
3
+ metadata.gz: 25d057964fdb1ec9c73f9652e2ce82974abf5309b52047e0927a51d51aed78da
4
+ data.tar.gz: 2c6e90b8c9db02dfb442e65a2bd11d5e6fa690f9e9772d30545a9feba6c9a9c4
5
5
  SHA512:
6
- metadata.gz: 7a385dc9d6248fe3324e57bdad08937df3f78099e5dacaa479a0846138c141323e5ef118d3f3320ea793601a562863f1abd17656c02cd31dae554ca89e36adac
7
- data.tar.gz: c5b315e5a7700523bbe242ebb74dd7c9068c30042a044c635fa17d55cf10f470d360eb883c13d53cb3059ef87cea63d322fc7baa731c3808e7c7fd11c5346587
6
+ metadata.gz: 8cd5595a0644d74eafcb7e859576ab24e9aaf4e684981cbe5d4ec9039fd425cb5659c6c33c5b9111f22c7e9ba6d8cc5d46821341c4124f22670c15cb54e4df69
7
+ data.tar.gz: 844a1e91ec57d11b867aa8c9d381661e6e560bc5d4f1b797bf363eb66528fbf345f9ff324214a1bcafd50b125aa789c5961bda732e7d0c056fd5e4fe7738700e
data/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  All notable changes to `addressing` will be documented in this file.
4
4
 
5
+ ## 1.1.0 (2025-10-03)
6
+
7
+ - Add formatter-level caching for Country.list
8
+ - Refactor FieldOverrides initialization to be more idiomatic
9
+ - Add equality methods to Address class
10
+ - Refactor complex values method in DefaultFormatter
11
+ - Extract magic strings to constants
12
+ - Simplify boolean validation in formatter options
13
+ - use .new() for UnknownLocaleError
14
+ - Added RDoc documentation
15
+ - Replaced class variables with class instance variables
16
+ - Add custom exception classes for better error handling
17
+ - Refactor Address class
18
+ - Fix test bug in address_test.rb
19
+ - Remove duplicate code in Model validation
20
+ - add missing % to generic address format
21
+
22
+ ## 1.0.0 (2024-10-21)
23
+
24
+ - Sync data with commerceguys repository (v2.2.2)
25
+ - Drop support for Ruby 3.0
26
+
5
27
  ## 0.7.0 (2024-01-30)
6
28
 
7
29
  - Update subdivisions for Philippines (PH)
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A Ruby addressing library, powered by CLDR and Google's address data.
4
4
 
5
- - Countries, with translations for over 250 locales. Powered by [CLDR](http://cldr.unicode.org) v44.
5
+ - Countries, with translations for over 250 locales. Powered by [CLDR](http://cldr.unicode.org) v45.
6
6
  - Address formats for over 200 countries.
7
7
  - Subdivisions (administrative areas, localities, dependent localities) for 60 countries.
8
8
  - Both latin and local subdivision names, when relevant (e.g: Okinawa / 沖縄県).
Binary file
@@ -13,7 +13,7 @@
13
13
  {"country_code":"AX","format":"%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","required_fields":["address_line1","locality","postal_code"],"postal_code_pattern":"22\\d{3}","postal_code_prefix":"AX-"}
14
14
  {"country_code":"AZ","format":"%given_name %additional_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"\\d{4}","postal_code_prefix":"AZ "}
15
15
  {"country_code":"BA","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"\\d{5}"}
16
- {"country_code":"BB","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality, %administrative_area %postal_code","administrative_area_type":"parish","postal_code_pattern":"BB\\d{5}","subdivision_depth":1}
16
+ {"country_code":"BB","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality, %administrative_area %postal_code","required_fields":["address_line1","locality","administrative_area"],"administrative_area_type":"parish","postal_code_pattern":"BB\\d{5}","subdivision_depth":1}
17
17
  {"country_code":"BD","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality - %postal_code","postal_code_pattern":"\\d{4}"}
18
18
  {"country_code":"BE","format":"%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","required_fields":["address_line1","locality","postal_code"],"postal_code_pattern":"\\d{4}"}
19
19
  {"country_code":"BF","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality %sorting_code"}
@@ -32,12 +32,12 @@
32
32
  {"country_code":"CC","format":"%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%address_line3\n%locality %postal_code","uppercase_fields":["locality"],"postal_code_pattern":"6799"}
33
33
  {"country_code":"CH","format":"%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","required_fields":["address_line1","locality","postal_code"],"uppercase_fields":[],"postal_code_pattern":"\\d{4}"}
34
34
  {"country_code":"CI","format":"%given_name %family_name\n%organization\n%sorting_code %address_line1\n%address_line2\n%address_line3 %locality %sorting_code"}
35
- {"country_code":"CL","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality\n%administrative_area","postal_code_pattern":"\\d{7}","subdivision_depth":2}
35
+ {"country_code":"CL","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality\n%administrative_area","required_fields":["address_line1","locality","administrative_area"],"administrative_area_type":"region","postal_code_pattern":"\\d{7}","subdivision_depth":2}
36
36
  {"country_code":"CN","locale":"zh-Hans","format":"%family_name %given_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%dependent_locality\n%locality\n%administrative_area, %postal_code","local_format":"%postal_code\n%administrative_area%locality%dependent_locality\n%address_line1\n%address_line2\n%address_line3\n%organization\n%family_name %given_name","required_fields":["address_line1","locality","administrative_area","postal_code"],"uppercase_fields":["administrative_area"],"dependent_locality_type":"district","postal_code_pattern":"\\d{6}","subdivision_depth":3}
37
37
  {"country_code":"CO","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality, %administrative_area, %postal_code","required_fields":["address_line1","locality","administrative_area"],"administrative_area_type":"department","postal_code_pattern":"\\d{6}","subdivision_depth":1}
38
38
  {"country_code":"CR","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%administrative_area, %locality\n%postal_code","required_fields":["address_line1","locality","administrative_area"],"postal_code_pattern":"\\d{4,5}|\\d{3}-\\d{4}","subdivision_depth":1}
39
39
  {"country_code":"CU","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality %administrative_area\n%postal_code","postal_code_pattern":"\\d{5}","subdivision_depth":1}
40
- {"country_code":"CV","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality\n%administrative_area","administrative_area_type":"island","postal_code_pattern":"\\d{4}","subdivision_depth":1}
40
+ {"country_code":"CV","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality\n%administrative_area","required_fields":["address_line1","locality","administrative_area"],"administrative_area_type":"island","postal_code_pattern":"\\d{4}","subdivision_depth":1}
41
41
  {"country_code":"CX","format":"%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%address_line3\n%locality %postal_code","uppercase_fields":["locality"],"postal_code_pattern":"6798"}
42
42
  {"country_code":"CY","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"\\d{4}"}
43
43
  {"country_code":"CZ","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","required_fields":["address_line1","locality","postal_code"],"postal_code_pattern":"\\d{3} ?\\d{2}"}
@@ -47,7 +47,7 @@
47
47
  {"country_code":"DZ","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"\\d{5}"}
48
48
  {"country_code":"EC","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code\n%locality","uppercase_fields":["locality","postal_code"],"postal_code_pattern":"\\d{6}"}
49
49
  {"country_code":"EE","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality %administrative_area","required_fields":["address_line1","locality","postal_code"],"administrative_area_type":"county","postal_code_pattern":"\\d{5}","subdivision_depth":1}
50
- {"country_code":"EG","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%administrative_area\n%postal_code","postal_code_pattern":"\\d{5}","subdivision_depth":1}
50
+ {"country_code":"EG","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%administrative_area\n%postal_code","required_fields":["address_line1","locality","administrative_area"],"postal_code_pattern":"\\d{5}","subdivision_depth":1}
51
51
  {"country_code":"EH","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"\\d{5}"}
52
52
  {"country_code":"ES","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality %administrative_area","required_fields":["address_line1","locality","administrative_area","postal_code"],"uppercase_fields":["locality","administrative_area"],"postal_code_pattern":"\\d{5}","subdivision_depth":1}
53
53
  {"country_code":"ET","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"\\d{4}"}
@@ -82,7 +82,7 @@
82
82
  {"country_code":"IN","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality %postal_code\n%administrative_area","required_fields":["address_line1","locality","administrative_area","postal_code"],"administrative_area_type":"state","postal_code_type":"pin","postal_code_pattern":"\\d{6}","subdivision_depth":1}
83
83
  {"country_code":"IO","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%postal_code","required_fields":["address_line1","locality","postal_code"],"uppercase_fields":["locality","postal_code"],"postal_code_pattern":"BBND 1ZZ"}
84
84
  {"country_code":"IQ","format":"%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%address_line3\n%locality, %administrative_area\n%postal_code","required_fields":["address_line1","locality","administrative_area"],"uppercase_fields":["locality","administrative_area"],"postal_code_pattern":"\\d{5}"}
85
- {"country_code":"IR","format":"%organization\n%given_name %family_name\n%administrative_area\n%locality, %dependent_locality\n%address_line1\n%address_line2\n%address_line3\n%postal_code","dependent_locality_type":"neighborhood","postal_code_pattern":"\\d{5}-?\\d{5}","subdivision_depth":1}
85
+ {"country_code":"IR","format":"%organization\n%given_name %family_name\n%administrative_area\n%locality, %dependent_locality\n%address_line1\n%address_line2\n%address_line3\n%postal_code","required_fields":["address_line1","locality","administrative_area"],"dependent_locality_type":"neighborhood","postal_code_pattern":"\\d{5}-?\\d{5}","subdivision_depth":1}
86
86
  {"country_code":"IS","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"\\d{3}"}
87
87
  {"country_code":"IT","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality %administrative_area","required_fields":["address_line1","locality","administrative_area","postal_code"],"uppercase_fields":["locality","administrative_area"],"postal_code_pattern":"\\d{5}","subdivision_depth":1}
88
88
  {"country_code":"JE","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%postal_code","required_fields":["address_line1","locality","postal_code"],"uppercase_fields":["locality","postal_code"],"postal_code_pattern":"JE\\d[\\dA-Z]? ?\\d[ABD-HJLN-UW-Z]{2}"}
@@ -92,7 +92,7 @@
92
92
  {"country_code":"KE","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%postal_code","postal_code_pattern":"\\d{5}"}
93
93
  {"country_code":"KG","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"\\d{6}"}
94
94
  {"country_code":"KH","format":"%family_name %given_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality %postal_code","postal_code_pattern":"\\d{5,6}"}
95
- {"country_code":"KI","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%administrative_area\n%locality","uppercase_fields":["address_line1","address_line2","address_line3","locality","family_name","additional_name","given_name","organization","administrative_area"],"administrative_area_type":"island","subdivision_depth":1}
95
+ {"country_code":"KI","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%administrative_area\n%locality","required_fields":["address_line1","locality","administrative_area"],"uppercase_fields":["address_line1","address_line2","address_line3","locality","family_name","additional_name","given_name","organization","administrative_area"],"administrative_area_type":"island","subdivision_depth":1}
96
96
  {"country_code":"KM","uppercase_fields":["address_line1","address_line2","address_line3","locality"]}
97
97
  {"country_code":"KN","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality, %administrative_area","required_fields":["address_line1","locality","administrative_area"],"administrative_area_type":"island","subdivision_depth":1}
98
98
  {"country_code":"KP","locale":"ko","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%administrative_area, %postal_code","local_format":"%postal_code\n%administrative_area\n%locality\n%address_line1\n%address_line2\n%address_line3\n%organization\n%given_name %family_name","subdivision_depth":1}
@@ -128,7 +128,7 @@
128
128
  {"country_code":"MV","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality %postal_code","postal_code_pattern":"\\d{5}"}
129
129
  {"country_code":"MW","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality %sorting_code"}
130
130
  {"country_code":"MX","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%dependent_locality\n%postal_code %locality, %administrative_area","required_fields":["address_line1","locality","administrative_area","postal_code"],"uppercase_fields":["locality","administrative_area","postal_code"],"administrative_area_type":"state","dependent_locality_type":"neighborhood","postal_code_pattern":"\\d{5}","subdivision_depth":1}
131
- {"country_code":"MY","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%dependent_locality\n%postal_code %locality\n%administrative_area","required_fields":["address_line1","locality","postal_code"],"uppercase_fields":["locality","administrative_area"],"administrative_area_type":"state","dependent_locality_type":"village_township","postal_code_pattern":"\\d{5}","subdivision_depth":1}
131
+ {"country_code":"MY","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%dependent_locality\n%postal_code %locality\n%administrative_area","required_fields":["address_line1","locality","administrative_area","postal_code"],"uppercase_fields":["locality","administrative_area"],"administrative_area_type":"state","dependent_locality_type":"village_township","postal_code_pattern":"\\d{5}","subdivision_depth":1}
132
132
  {"country_code":"MZ","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality%administrative_area","postal_code_pattern":"\\d{4}","subdivision_depth":1}
133
133
  {"country_code":"NA","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%postal_code","postal_code_pattern":"\\d{5}"}
134
134
  {"country_code":"NC","format":"%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","required_fields":["address_line1","locality","postal_code"],"uppercase_fields":["address_line1","address_line2","address_line3","locality"],"postal_code_pattern":"988\\d{2}"}
@@ -143,7 +143,7 @@
143
143
  {"country_code":"NZ","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%dependent_locality\n%locality %postal_code","required_fields":["address_line1","locality","postal_code"],"locality_type":"town_city","postal_code_pattern":"\\d{4}"}
144
144
  {"country_code":"OM","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code\n%locality","postal_code_pattern":"(?:PC )?\\d{3}"}
145
145
  {"country_code":"PA","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%administrative_area","uppercase_fields":["locality","administrative_area"],"subdivision_depth":1}
146
- {"country_code":"PE","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality %postal_code\n%administrative_area","locality_type":"district","postal_code_pattern":"(?:LIMA \\d{1,2}|CALLAO 0?\\d)|[0-2]\\d{4}","subdivision_depth":1}
146
+ {"country_code":"PE","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality %postal_code\n%administrative_area","required_fields":["address_line1","locality","administrative_area"],"locality_type":"district","postal_code_pattern":"[0-2]\\d{4}","subdivision_depth":1}
147
147
  {"country_code":"PF","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality %administrative_area","required_fields":["address_line1","locality","administrative_area","postal_code"],"uppercase_fields":["locality","administrative_area"],"administrative_area_type":"island","postal_code_pattern":"987\\d{2}"}
148
148
  {"country_code":"PG","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality %postal_code %administrative_area","required_fields":["address_line1","locality","administrative_area"],"postal_code_pattern":"\\d{3}","subdivision_depth":1}
149
149
  {"country_code":"PH","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%dependent_locality, %locality\n%postal_code %administrative_area","postal_code_pattern":"\\d{4}","subdivision_depth":1}
@@ -179,7 +179,7 @@
179
179
  {"country_code":"SZ","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%postal_code","uppercase_fields":["address_line1","address_line2","address_line3","locality","postal_code"],"postal_code_pattern":"[HLMS]\\d{3}"}
180
180
  {"country_code":"TA","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%postal_code","postal_code_pattern":"TDCU 1ZZ"}
181
181
  {"country_code":"TC","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%postal_code","required_fields":["address_line1","locality","postal_code"],"uppercase_fields":["locality","postal_code"],"postal_code_pattern":"TKCA 1ZZ"}
182
- {"country_code":"TH","locale":"th","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%dependent_locality, %locality\n%administrative_area %postal_code","local_format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%dependent_locality %locality\n%administrative_area %postal_code","uppercase_fields":["administrative_area"],"postal_code_pattern":"\\d{5}","subdivision_depth":1}
182
+ {"country_code":"TH","locale":"th","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%dependent_locality, %locality\n%administrative_area %postal_code","local_format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%dependent_locality %locality\n%administrative_area %postal_code","required_fields":["address_line1","locality","administrative_area"],"uppercase_fields":["administrative_area"],"postal_code_pattern":"\\d{5}","subdivision_depth":1}
183
183
  {"country_code":"TJ","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"\\d{6}"}
184
184
  {"country_code":"TM","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"\\d{6}"}
185
185
  {"country_code":"TN","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"\\d{4}"}
@@ -197,7 +197,7 @@
197
197
  {"country_code":"VE","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality %postal_code, %administrative_area","required_fields":["address_line1","locality","administrative_area"],"uppercase_fields":["locality","administrative_area"],"administrative_area_type":"state","postal_code_pattern":"\\d{4}","subdivision_depth":1}
198
198
  {"country_code":"VG","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%postal_code","required_fields":["address_line1"],"postal_code_pattern":"VG\\d{4}"}
199
199
  {"country_code":"VI","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality %postal_code","required_fields":["address_line1","locality","postal_code"],"uppercase_fields":["address_line1","address_line2","address_line3","locality","family_name","additional_name","given_name","organization"],"postal_code_type":"zip","postal_code_pattern":"(008(?:(?:[0-4]\\d)|(?:5[01])))(?:[ \\-](\\d{4}))?"}
200
- {"country_code":"VN","format":"%family_name %given_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%administrative_area %postal_code","postal_code_pattern":"\\d{5}\\d?","subdivision_depth":1}
200
+ {"country_code":"VN","format":"%family_name %given_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality\n%administrative_area %postal_code","required_fields":["address_line1","locality","administrative_area"],"postal_code_pattern":"\\d{5}\\d?","subdivision_depth":1}
201
201
  {"country_code":"WF","format":"%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","required_fields":["address_line1","locality","postal_code"],"uppercase_fields":["address_line1","address_line2","address_line3","locality"],"postal_code_pattern":"986\\d{2}"}
202
202
  {"country_code":"XK","format":"%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","postal_code_pattern":"[1-7]\\d{4}"}
203
203
  {"country_code":"YT","format":"%organization\n%given_name %family_name\n%address_line1\n%address_line2\n%address_line3\n%postal_code %locality","required_fields":["address_line1","locality","postal_code"],"uppercase_fields":["address_line1","address_line2","address_line3","locality"],"postal_code_pattern":"976\\d{2}"}
@@ -25,6 +25,7 @@
25
25
  "BZ": "Belize",
26
26
  "BJ": "Benin",
27
27
  "BM": "Bermuda",
28
+ "IN": "Bharat",
28
29
  "BT": "Bhutan",
29
30
  "BO": "Bolivia",
30
31
  "BA": "Bosnia & Herzegovina",
@@ -107,7 +108,6 @@
107
108
  "HK": "Hong Kong SAR China",
108
109
  "HU": "Hungary",
109
110
  "IS": "Iceland",
110
- "IN": "India",
111
111
  "ID": "Indonesia",
112
112
  "IR": "Iran",
113
113
  "IQ": "Iraq",
@@ -3,115 +3,115 @@
3
3
  "locale": "uk",
4
4
  "subdivisions": {
5
5
  "43": {
6
- "name": "Crimea",
6
+ "name": "Avtonomna Respublika Krym",
7
7
  "local_name": "Автономна Республіка Крим"
8
8
  },
9
9
  "05": {
10
- "name": "Vinnyts'ka oblast",
10
+ "name": "Vinnytska oblast",
11
11
  "local_name": "Вінницька область"
12
12
  },
13
13
  "07": {
14
- "name": "Volyns'ka oblast",
14
+ "name": "Volynska oblast",
15
15
  "local_name": "Волинська область"
16
16
  },
17
17
  "12": {
18
- "name": "Dnipropetrovsk oblast",
18
+ "name": "Dnipropetrovska oblast",
19
19
  "local_name": "Дніпропетровська область"
20
20
  },
21
21
  "14": {
22
- "name": "Donetsk oblast",
22
+ "name": "Donetska oblast",
23
23
  "local_name": "Донецька область"
24
24
  },
25
25
  "18": {
26
- "name": "Zhytomyrs'ka oblast",
26
+ "name": "Zhytomyrska oblast",
27
27
  "local_name": "Житомирська область"
28
28
  },
29
29
  "21": {
30
- "name": "Zakarpats'ka oblast",
30
+ "name": "Zakarpatska oblast",
31
31
  "local_name": "Закарпатська область"
32
32
  },
33
33
  "23": {
34
- "name": "Zaporiz'ka oblast",
34
+ "name": "Zaporizka oblast",
35
35
  "local_name": "Запорізька область"
36
36
  },
37
37
  "26": {
38
- "name": "Ivano-Frankivs'ka oblast",
38
+ "name": "Ivano-Frankivska oblast",
39
39
  "local_name": "Івано-Франківська область"
40
40
  },
41
41
  "30": {
42
- "code": "Kyiv city",
43
- "local_code": "місто Київ",
44
- "name": "Kyiv city",
42
+ "code": "Misto Kyiv",
43
+ "local_code": "Місто Київ",
44
+ "name": "Kyiv",
45
45
  "local_name": "Київ"
46
46
  },
47
47
  "32": {
48
- "name": "Kiev oblast",
48
+ "name": "Kyivska oblast",
49
49
  "local_name": "Київська область"
50
50
  },
51
51
  "35": {
52
- "name": "Kirovohrads'ka oblast",
52
+ "name": "Kirovohradska oblast",
53
53
  "local_name": "Кіровоградська область"
54
54
  },
55
55
  "09": {
56
- "name": "Luhans'ka oblast",
56
+ "name": "Luhanska oblast",
57
57
  "local_name": "Луганська область"
58
58
  },
59
59
  "46": {
60
- "name": "Lviv oblast",
60
+ "name": "Lvivska oblast",
61
61
  "local_name": "Львівська область"
62
62
  },
63
63
  "48": {
64
- "name": "Mykolaivs'ka oblast",
64
+ "name": "Mykolaivska oblast",
65
65
  "local_name": "Миколаївська область"
66
66
  },
67
67
  "51": {
68
- "name": "Odessa oblast",
68
+ "name": "Odeska oblast",
69
69
  "local_name": "Одеська область"
70
70
  },
71
71
  "53": {
72
- "name": "Poltavs'ka oblast",
72
+ "name": "Poltavska oblast",
73
73
  "local_name": "Полтавська область"
74
74
  },
75
75
  "56": {
76
- "name": "Rivnens'ka oblast",
76
+ "name": "Rivnenska oblast",
77
77
  "local_name": "Рівненська область"
78
78
  },
79
79
  "40": {
80
- "code": "Sevastopol' city",
81
- "local_code": "місто Севастополь",
82
- "name": "Sevastopol' city",
80
+ "code": "Misto Sevastopol",
81
+ "local_code": "Місто Севастополь",
82
+ "name": "Sevastopol",
83
83
  "local_name": "Севастополь"
84
84
  },
85
85
  "59": {
86
- "name": "Sums'ka oblast",
86
+ "name": "Sumska oblast",
87
87
  "local_name": "Сумська область"
88
88
  },
89
89
  "61": {
90
- "name": "Ternopil's'ka oblast",
90
+ "name": "Ternopilska oblast",
91
91
  "local_name": "Тернопільська область"
92
92
  },
93
93
  "63": {
94
- "name": "Kharkiv oblast",
94
+ "name": "Kharkivska oblast",
95
95
  "local_name": "Харківська область"
96
96
  },
97
97
  "65": {
98
- "name": "Khersons'ka oblast",
98
+ "name": "Khersonska oblast",
99
99
  "local_name": "Херсонська область"
100
100
  },
101
101
  "68": {
102
- "name": "Khmel'nyts'ka oblast",
102
+ "name": "Khmelnytska oblast",
103
103
  "local_name": "Хмельницька область"
104
104
  },
105
105
  "71": {
106
- "name": "Cherkas'ka oblast",
106
+ "name": "Cherkaska oblast",
107
107
  "local_name": "Черкаська область"
108
108
  },
109
109
  "77": {
110
- "name": "Chernivets'ka oblast",
110
+ "name": "Chernivetska oblast",
111
111
  "local_name": "Чернівецька область"
112
112
  },
113
113
  "74": {
114
- "name": "Chernihivs'ka oblast",
114
+ "name": "Chernihivska oblast",
115
115
  "local_name": "Чернігівська область"
116
116
  }
117
117
  }
@@ -1,9 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Addressing
4
+ # Represents a postal address with attributes for country, administrative areas,
5
+ # postal code, address lines, and recipient information.
6
+ #
7
+ # Address objects are immutable - use the +with_*+ methods to create modified copies.
8
+ #
9
+ # @example Creating an address
10
+ # address = Addressing::Address.new(
11
+ # country_code: "US",
12
+ # administrative_area: "CA",
13
+ # locality: "Mountain View",
14
+ # postal_code: "94043",
15
+ # address_line1: "1600 Amphitheatre Parkway"
16
+ # )
17
+ #
18
+ # @example Modifying an address
19
+ # updated = address.with_postal_code("94044")
4
20
  class Address
5
- attr_reader :country_code, :administrative_area, :locality, :dependent_locality, :postal_code, :sorting_code, :address_line1, :address_line2, :address_line3, :organization, :given_name, :additional_name, :family_name, :locale
6
-
21
+ FIELDS = %i[
22
+ country_code administrative_area locality dependent_locality
23
+ postal_code sorting_code address_line1 address_line2 address_line3
24
+ organization given_name additional_name family_name locale
25
+ ].freeze
26
+
27
+ attr_reader(*FIELDS)
28
+
29
+ # Creates a new Address instance.
30
+ #
31
+ # @param country_code [String] ISO 3166-1 alpha-2 country code
32
+ # @param administrative_area [String] Top-level administrative subdivision (state, province, etc.)
33
+ # @param locality [String] City or locality
34
+ # @param dependent_locality [String] Dependent locality (neighborhood, suburb, district, etc.)
35
+ # @param postal_code [String] Postal code
36
+ # @param sorting_code [String] Sorting code (used in some countries)
37
+ # @param address_line1 [String] First line of the street address
38
+ # @param address_line2 [String] Second line of the street address
39
+ # @param address_line3 [String] Third line of the street address
40
+ # @param organization [String] Organization name
41
+ # @param given_name [String] Given name (first name)
42
+ # @param additional_name [String] Additional name (middle name, patronymic)
43
+ # @param family_name [String] Family name (last name)
44
+ # @param locale [String] Locale code for the address
7
45
  def initialize(country_code: "", administrative_area: "", locality: "", dependent_locality: "", postal_code: "", sorting_code: "", address_line1: "", address_line2: "", address_line3: "", organization: "", given_name: "", additional_name: "", family_name: "", locale: "und")
8
46
  @country_code = country_code
9
47
  @administrative_area = administrative_area
@@ -21,92 +59,42 @@ module Addressing
21
59
  @locale = locale
22
60
  end
23
61
 
24
- def with_country_code(country_code)
25
- address = clone
26
- address.country_code = country_code
27
- address
28
- end
29
-
30
- def with_administrative_area(administrative_area)
31
- address = clone
32
- address.administrative_area = administrative_area
33
- address
34
- end
35
-
36
- def with_locality(locality)
37
- address = clone
38
- address.locality = locality
39
- address
40
- end
41
-
42
- def with_dependent_locality(dependent_locality)
43
- address = clone
44
- address.dependent_locality = dependent_locality
45
- address
46
- end
47
-
48
- def with_postal_code(postal_code)
49
- address = clone
50
- address.postal_code = postal_code
51
- address
52
- end
53
-
54
- def with_sorting_code(sorting_code)
55
- address = clone
56
- address.sorting_code = sorting_code
57
- address
58
- end
59
-
60
- def with_address_line1(address_line1)
61
- address = clone
62
- address.address_line1 = address_line1
63
- address
64
- end
65
-
66
- def with_address_line2(address_line2)
67
- address = clone
68
- address.address_line2 = address_line2
69
- address
62
+ # Define with_* methods for all fields
63
+ FIELDS.each do |field|
64
+ define_method(:"with_#{field}") do |value|
65
+ address = clone
66
+ address.send(:"#{field}=", value)
67
+ address
68
+ end
70
69
  end
71
70
 
72
- def with_address_line3(address_line3)
73
- address = clone
74
- address.address_line3 = address_line3
75
- address
76
- end
77
-
78
- def with_organization(organization)
79
- address = clone
80
- address.organization = organization
81
- address
82
- end
83
-
84
- def with_given_name(given_name)
85
- address = clone
86
- address.given_name = given_name
87
- address
88
- end
71
+ # Compares two addresses for equality based on all field values.
72
+ #
73
+ # @param other [Object] The object to compare with
74
+ # @return [Boolean] true if all fields are equal
75
+ def ==(other)
76
+ return false unless other.is_a?(Address)
89
77
 
90
- def with_additional_name(additional_name)
91
- address = clone
92
- address.additional_name = additional_name
93
- address
78
+ FIELDS.all? { |field| send(field) == other.send(field) }
94
79
  end
95
80
 
96
- def with_family_name(family_name)
97
- address = clone
98
- address.family_name = family_name
99
- address
81
+ # Compares two addresses for equality (alias for ==).
82
+ #
83
+ # @param other [Object] The object to compare with
84
+ # @return [Boolean] true if all fields are equal
85
+ def eql?(other)
86
+ self == other
100
87
  end
101
88
 
102
- def with_locale(locale)
103
- address = clone
104
- address.locale = locale
105
- address
89
+ # Generates a hash code for the address based on all field values.
90
+ #
91
+ # @return [Integer] hash code
92
+ def hash
93
+ FIELDS.map { |field| send(field) }.hash
106
94
  end
107
95
 
108
96
  protected
109
97
 
110
- attr_writer :country_code, :administrative_area, :locality, :dependent_locality, :postal_code, :sorting_code, :address_line1, :address_line2, :address_line3, :organization, :given_name, :additional_name, :family_name, :locale
98
+ attr_writer(*FIELDS)
111
99
  end
112
100
  end
@@ -1,20 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Addressing
4
+ # Provides address format information for countries.
5
+ #
6
+ # Address formats define which fields are used, their order, requirements,
7
+ # and formatting rules for postal addresses in different countries.
8
+ #
9
+ # @example Get address format for Brazil
10
+ # format = Addressing::AddressFormat.get('BR')
11
+ # format.used_fields # => ["given_name", "family_name", ...]
12
+ # format.required_fields # => ["address_line1", "locality", ...]
4
13
  class AddressFormat
5
14
  class << self
6
- # The instantiated address formats, keyed by country code.
7
- @@address_formats = {}
8
-
15
+ # Gets the address format for the provided country code.
16
+ #
17
+ # @param country_code [String] ISO 3166-1 alpha-2 country code
18
+ # @return [AddressFormat] Address format instance
9
19
  def get(country_code)
10
20
  country_code = country_code.upcase
21
+ @address_formats ||= {}
11
22
 
12
- unless @@address_formats.key?(country_code)
23
+ unless @address_formats.key?(country_code)
13
24
  definition = process_definition(definitions[country_code] || {country_code: country_code})
14
- @@address_formats[country_code] = new(definition)
25
+ @address_formats[country_code] = new(definition)
15
26
  end
16
27
 
17
- @@address_formats[country_code]
28
+ @address_formats[country_code]
18
29
  end
19
30
 
20
31
  def all
@@ -27,7 +38,7 @@ module Addressing
27
38
  private
28
39
 
29
40
  def definitions
30
- @@definitions ||= Marshal.load(File.read(File.expand_path("../../../data/address_formats.dump", __FILE__).to_s))
41
+ @definitions ||= Marshal.load(File.read(File.expand_path("../../../data/address_formats.dump", __FILE__).to_s))
31
42
  end
32
43
 
33
44
  def process_definition(definition)
@@ -42,7 +53,7 @@ module Addressing
42
53
 
43
54
  def generic_definition
44
55
  {
45
- format: "%given_name %family_name\n%organization\n%address_line1\n%address_line2\n\address_line3\n%locality",
56
+ format: "%given_name %family_name\n%organization\n%address_line1\n%address_line2\n%address_line3\n%locality",
46
57
  required_fields: [
47
58
  "address_line1", "locality"
48
59
  ],
@@ -1,11 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Addressing
4
+ # Provides country information including names, codes, currency, and timezones.
5
+ #
6
+ # Country names are available in over 250 locales powered by CLDR data.
7
+ #
8
+ # @example Get a country by code
9
+ # brazil = Addressing::Country.get('BR')
10
+ # brazil.name # => "Brazil"
11
+ # brazil.currency_code # => "BRL"
12
+ #
13
+ # @example Get all countries
14
+ # countries = Addressing::Country.all('fr-FR')
15
+ #
16
+ # @example Get a simple list of countries
17
+ # list = Addressing::Country.list('en')
4
18
  class Country
5
19
  class << self
6
- @@definitions = {}
7
-
8
- @@available_locales = [
20
+ AVAILABLE_LOCALES = [
9
21
  "af", "am", "ar", "ar-LY", "ar-SA", "as", "az", "be", "bg", "bn",
10
22
  "bn-IN", "bs", "ca", "chr", "cs", "cy", "da", "de", "de-AT", "de-CH",
11
23
  "dsb", "el", "el-polyton", "en", "en-001", "en-AU", "en-CA", "en-ID",
@@ -23,12 +35,19 @@ module Addressing
23
35
  "uz", "vi", "yue", "yue-Hans", "zh", "zh-Hant", "zh-Hant-HK", "zu"
24
36
  ]
25
37
 
38
+ # Gets a Country instance for the provided country code.
39
+ #
40
+ # @param country_code [String] ISO 3166-1 alpha-2 country code
41
+ # @param locale [String] Locale for the country name (default: "en")
42
+ # @param fallback_locale [String] Fallback locale if requested locale is unavailable
43
+ # @return [Country] Country instance
44
+ # @raise [UnknownCountryError] if the country code is not recognized
26
45
  def get(country_code, locale = "en", fallback_locale = "en")
27
46
  country_code = country_code.upcase
28
47
 
29
- raise UnknownCountryError, country_code unless base_definitions.key?(country_code)
48
+ raise UnknownCountryError.new(country_code) unless base_definitions.key?(country_code)
30
49
 
31
- locale = Locale.resolve(@@available_locales, locale, fallback_locale)
50
+ locale = Locale.resolve(AVAILABLE_LOCALES, locale, fallback_locale)
32
51
  definitions = load_definitions(locale)
33
52
 
34
53
  new(
@@ -41,8 +60,13 @@ module Addressing
41
60
  )
42
61
  end
43
62
 
63
+ # Gets all Country instances.
64
+ #
65
+ # @param locale [String] Locale for country names (default: "en")
66
+ # @param fallback_locale [String] Fallback locale if requested locale is unavailable
67
+ # @return [Hash<String, Country>] Hash of country code => Country instance
44
68
  def all(locale = "en", fallback_locale = "en")
45
- locale = Locale.resolve(@@available_locales, locale, fallback_locale)
69
+ locale = Locale.resolve(AVAILABLE_LOCALES, locale, fallback_locale)
46
70
  definitions = load_definitions(locale)
47
71
 
48
72
  definitions.map do |country_code, country_name|
@@ -59,8 +83,13 @@ module Addressing
59
83
  end.to_h
60
84
  end
61
85
 
86
+ # Gets a list of country codes and names.
87
+ #
88
+ # @param locale [String] Locale for country names (default: "en")
89
+ # @param fallback_locale [String] Fallback locale if requested locale is unavailable
90
+ # @return [Hash<String, String>] Hash of country code => country name
62
91
  def list(locale = "en", fallback_locale = "en")
63
- locale = Locale.resolve(@@available_locales, locale, fallback_locale)
92
+ locale = Locale.resolve(AVAILABLE_LOCALES, locale, fallback_locale)
64
93
  definitions = load_definitions(locale)
65
94
 
66
95
  definitions.map do |country_code, country_name|
@@ -72,19 +101,20 @@ module Addressing
72
101
 
73
102
  # Loads the country definitions for the provided locale.
74
103
  def load_definitions(locale)
75
- unless @@definitions.key?(locale)
104
+ @definitions ||= {}
105
+ unless @definitions.key?(locale)
76
106
  filename = File.join(File.expand_path("../../../data/country", __FILE__).to_s, "#{locale}.json")
77
- @@definitions[locale] = JSON.parse(File.read(filename))
107
+ @definitions[locale] = JSON.parse(File.read(filename))
78
108
  end
79
109
 
80
- @@definitions[locale]
110
+ @definitions[locale]
81
111
  end
82
112
 
83
113
  # Gets the base country definitions.
84
114
  #
85
115
  # Contains data common to all locales: three letter code, numeric code.
86
116
  def base_definitions
87
- @@base_definitions ||= {
117
+ @base_definitions ||= {
88
118
  "AC" => ["ASC", nil, "SHP"],
89
119
  "AD" => ["AND", "020", "EUR"],
90
120
  "AE" => ["ARE", "784", "AED"],
@@ -139,7 +169,7 @@ module Addressing
139
169
  "CR" => ["CRI", "188", "CRC"],
140
170
  "CU" => ["CUB", "192", "CUP"],
141
171
  "CV" => ["CPV", "132", "CVE"],
142
- "CW" => ["CUW", "531", "ANG"],
172
+ "CW" => ["CUW", "531", "XCG"],
143
173
  "CX" => ["CXR", "162", "AUD"],
144
174
  "CY" => ["CYP", "196", "EUR"],
145
175
  "CZ" => ["CZE", "203", "CZK"],
@@ -300,7 +330,7 @@ module Addressing
300
330
  "SS" => ["SSD", "728", "SSP"],
301
331
  "ST" => ["STP", "678", "STN"],
302
332
  "SV" => ["SLV", "222", "USD"],
303
- "SX" => ["SXM", "534", "ANG"],
333
+ "SX" => ["SXM", "534", "XCG"],
304
334
  "SY" => ["SYR", "760", "SYP"],
305
335
  "SZ" => ["SWZ", "748", "SZL"],
306
336
  "TA" => ["TAA", nil, "GBP"],
@@ -2,8 +2,13 @@
2
2
 
3
3
  module Addressing
4
4
  class DefaultFormatter
5
+ DEFAULT_LOCALE = "en"
6
+ FORMAT_PLACEHOLDER_PATTERN = /%[a-z1-9_]+/
7
+ LEADING_TRAILING_PUNCTUATION_PATTERN = /\A[ \-,]+|[ \-,]+\z/
8
+ MULTIPLE_SPACES_PATTERN = /\s\s+/
9
+
5
10
  DEFAULT_OPTIONS = {
6
- locale: "en",
11
+ locale: DEFAULT_LOCALE,
7
12
  html: true,
8
13
  html_tag: "p",
9
14
  html_attributes: {translate: "no"}
@@ -13,6 +18,7 @@ module Addressing
13
18
  assert_options(default_options)
14
19
 
15
20
  @default_options = self.class::DEFAULT_OPTIONS.merge(default_options)
21
+ @country_list_cache = {}
16
22
  end
17
23
 
18
24
  def format(address, options = {})
@@ -33,7 +39,7 @@ module Addressing
33
39
  view = render_view(view)
34
40
 
35
41
  replacements = view.map { |key, element| ["%#{key}", element] }.to_h
36
- output = format_string.gsub(/%[a-z1-9_]+/) { |m| replacements[m] }
42
+ output = format_string.gsub(FORMAT_PLACEHOLDER_PATTERN) { |m| replacements[m] }
37
43
  output = clean_output(output)
38
44
 
39
45
  if options[:html]
@@ -49,7 +55,7 @@ module Addressing
49
55
 
50
56
  # Builds the view for the given address.
51
57
  def build_view(address, address_format, options)
52
- countries = Country.list(options[:locale])
58
+ countries = country_list(options[:locale])
53
59
  values = values(address, address_format).merge({"country" => countries.key?(address.country_code) ? countries[address.country_code] : address.country_code})
54
60
  used_fields = address_format.used_fields + ["country"]
55
61
 
@@ -58,6 +64,11 @@ module Addressing
58
64
  end.to_h
59
65
  end
60
66
 
67
+ # Gets the country list for a locale, with caching.
68
+ def country_list(locale)
69
+ @country_list_cache[locale] ||= Country.list(locale)
70
+ end
71
+
61
72
  # Renders the given view.
62
73
  def render_view(view)
63
74
  view.map do |key, element|
@@ -90,27 +101,34 @@ module Addressing
90
101
 
91
102
  # Removes empty lines, leading punctuation, excess whitespace.
92
103
  def clean_output(output)
93
- output.split("\n").map { |line| line.gsub(/\A[ \-,]+|[ \-,]+\z/, "").strip.gsub(/\s\s+/, " ") }.reject(&:empty?).join("\n")
104
+ output.split("\n").map { |line| line.gsub(LEADING_TRAILING_PUNCTUATION_PATTERN, "").strip.gsub(MULTIPLE_SPACES_PATTERN, " ") }.reject(&:empty?).join("\n")
94
105
  end
95
106
 
96
107
  # Gets the address values used to build the view.
97
108
  def values(address, address_format)
98
- values = AddressField.all.map { |_, field| [field, address.send(field)] }.to_h
109
+ values = extract_address_values(address)
110
+ resolve_subdivision_values(values, address, address_format)
111
+ values
112
+ end
113
+
114
+ # Extracts all address field values.
115
+ def extract_address_values(address)
116
+ AddressField.all.map { |_, field| [field, address.send(field)] }.to_h
117
+ end
118
+
119
+ # Resolves subdivision values to their display codes.
120
+ def resolve_subdivision_values(values, address, address_format)
99
121
  subdivision_fields = address_format.used_subdivision_fields
100
122
 
101
123
  # Replace the subdivision values with the names of any predefined ones.
102
124
  subdivision_fields.each_with_index.inject([{}, []]) do |(original_values, parents), (field, index)|
103
- if values[field].nil?
104
- # This level is empty, so there can be no sublevels.
105
- break
106
- end
125
+ # This level is empty, so there can be no sublevels.
126
+ break if values[field].nil?
107
127
 
108
128
  parents << ((index > 0) ? original_values[subdivision_fields[index - 1]] : address.country_code)
109
129
 
110
130
  subdivision = Subdivision.get(values[field], parents)
111
- if subdivision.nil?
112
- break
113
- end
131
+ break if subdivision.nil?
114
132
 
115
133
  # Remember the original value so that it can be used for parents.
116
134
  original_values[field] = values[field]
@@ -119,15 +137,11 @@ module Addressing
119
137
  use_local_name = Locale.match_candidates(address.locale, subdivision.locale)
120
138
  values[field] = use_local_name ? subdivision.local_code : subdivision.code
121
139
 
122
- if !subdivision.children?
123
- # The current subdivision has no children, stop.
124
- break
125
- end
140
+ # The current subdivision has no children, stop.
141
+ break unless subdivision.children?
126
142
 
127
143
  [original_values, parents]
128
144
  end
129
-
130
- values
131
145
  end
132
146
 
133
147
  private
@@ -142,7 +156,7 @@ module Addressing
142
156
  end
143
157
  end
144
158
 
145
- if options.key?(:html) && !options[:html].is_a?(TrueClass) && !options[:html].is_a?(FalseClass)
159
+ if options.key?(:html) && ![true, false].include?(options[:html])
146
160
  raise ArgumentError, "The option `html` must be a boolean."
147
161
  end
148
162
 
@@ -3,15 +3,14 @@
3
3
  module Addressing
4
4
  class Enum
5
5
  class << self
6
- @@values = {}
7
-
8
6
  # Gets all available values.
9
7
  def all
10
- if !@@values.key?(name)
11
- @@values[name] = constants.map { |constant| [constant, const_get(constant)] }.to_h
8
+ @values ||= {}
9
+ if !@values.key?(name)
10
+ @values[name] = constants.map { |constant| [constant, const_get(constant)] }.to_h
12
11
  end
13
12
 
14
- @@values[name]
13
+ @values[name]
15
14
  end
16
15
 
17
16
  # Gets the key of the provided value.
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Addressing
4
+ # Base error class for all Addressing errors
5
+ class Error < StandardError; end
6
+
7
+ # Raised when an unknown country code is provided
8
+ class UnknownCountryError < Error
9
+ def initialize(country_code)
10
+ super("Unknown country code: #{country_code}")
11
+ @country_code = country_code
12
+ end
13
+
14
+ attr_reader :country_code
15
+ end
16
+
17
+ # Raised when an unknown locale is provided
18
+ class UnknownLocaleError < Error
19
+ def initialize(locale)
20
+ super("Unknown locale: #{locale}")
21
+ @locale = locale
22
+ end
23
+
24
+ attr_reader :locale
25
+ end
26
+
27
+ # Raised when an invalid argument is provided
28
+ class InvalidArgumentError < Error; end
29
+ end
@@ -14,20 +14,13 @@ module Addressing
14
14
  AddressField.assert_all_exist(definition.keys)
15
15
  FieldOverride.assert_all_exist(definition.values)
16
16
 
17
- @hidden_fields = []
18
- @optional_fields = []
19
- @required_fields = []
17
+ # Group fields by their override type
18
+ grouped = definition.group_by { |_field, override| override }
19
+ .transform_values { |pairs| pairs.map(&:first) }
20
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
21
+ @hidden_fields = grouped[FieldOverride::HIDDEN] || []
22
+ @optional_fields = grouped[FieldOverride::OPTIONAL] || []
23
+ @required_fields = grouped[FieldOverride::REQUIRED] || []
31
24
  end
32
25
  end
33
26
  end
@@ -251,7 +251,7 @@ module Addressing
251
251
  end
252
252
 
253
253
  # No locale could be resolved, stop here.
254
- raise UnknownLocaleError, locale if resolved_locale.nil?
254
+ raise UnknownLocaleError.new(locale) if resolved_locale.nil?
255
255
 
256
256
  resolved_locale
257
257
  end
@@ -20,12 +20,6 @@ module Addressing
20
20
  return unless address.country_code.present?
21
21
 
22
22
  address_format = AddressFormat.get(address.country_code)
23
- address_format.used_fields
24
-
25
- return unless address.country_code.present?
26
-
27
- address_format = AddressFormat.get(address.country_code)
28
- address_format.used_fields
29
23
 
30
24
  # Validate the presence of required fields.
31
25
  AddressFormatHelper.required_fields(address_format, field_overrides).each do |required_field|
@@ -3,7 +3,7 @@
3
3
  module Addressing
4
4
  class PostalLabelFormatter < DefaultFormatter
5
5
  DEFAULT_OPTIONS = {
6
- locale: "en",
6
+ locale: DEFAULT_LOCALE,
7
7
  html: false,
8
8
  html_tag: "p",
9
9
  html_attributes: {translate: "no"},
@@ -15,7 +15,7 @@ module Addressing
15
15
  def build_view(address, address_format, options)
16
16
  raise ArgumentError, "The origin_country option cannot be empty." if options[:origin_country].empty?
17
17
 
18
- view = super(address, address_format, options)
18
+ view = super
19
19
  view = view.map do |key, element|
20
20
  # Uppercase fields where required by the format.
21
21
  element[:value] = element[:value].upcase if address_format.uppercase_fields.include?(key)
@@ -1,25 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Addressing
4
+ # Represents administrative subdivisions within countries.
5
+ #
6
+ # Subdivisions can be hierarchical with up to three levels:
7
+ # Administrative Area -> Locality -> Dependent Locality
8
+ #
9
+ # @example Get subdivisions for Brazil
10
+ # states = Addressing::Subdivision.all(['BR'])
11
+ # states.each do |code, state|
12
+ # puts "#{code}: #{state.name}"
13
+ # municipalities = state.children
14
+ # end
15
+ #
16
+ # @example Get subdivisions for a Brazilian state
17
+ # municipalities = Addressing::Subdivision.all(['BR', 'CE'])
4
18
  class Subdivision
5
19
  class << self
6
- # Subdivision definitions.
7
- @@definitions = {}
8
-
9
- # Parent subdivisions.
20
+ # Gets a Subdivision instance by ID and parent hierarchy.
10
21
  #
11
- # Used as a cache to speed up instantiating subdivisions with the same
12
- # parent. Contains only parents instead of all instantiated subdivisions
13
- # to minimize duplicating the data in $this->definitions, thus reducing
14
- # memory usage.
15
- @@parents = {}
16
-
22
+ # @param id [String] Subdivision ID
23
+ # @param parents [Array<String>] Parent hierarchy (e.g., ['BR'] or ['BR', 'CE'])
24
+ # @return [Subdivision, nil] Subdivision instance or nil if not found
17
25
  def get(id, parents)
18
26
  definitions = load_definitions(parents)
19
27
  create_subdivision_from_definitions(id, definitions)
20
28
  end
21
29
 
22
30
  # Returns all subdivision instances for the provided parents.
31
+ #
32
+ # @param parents [Array<String>] Parent hierarchy (e.g., ['BR'] or ['BR', 'CE'])
33
+ # @return [Hash<String, Subdivision>] Hash of subdivision ID => Subdivision instance
23
34
  def all(parents)
24
35
  definitions = load_definitions(parents)
25
36
  return {} if definitions.empty?
@@ -57,9 +68,10 @@ module Addressing
57
68
  grandparents = parents.dup
58
69
  parent_id = grandparents.pop
59
70
  parent_group = build_group(grandparents.dup)
71
+ @definitions ||= {}
60
72
 
61
- if @@definitions.dig(parent_group, "subdivisions", parent_id)
62
- definition = @@definitions[parent_group]["subdivisions"][parent_id]
73
+ if @definitions.dig(parent_group, "subdivisions", parent_id)
74
+ definition = @definitions[parent_group]["subdivisions"][parent_id]
63
75
  return !!definition["has_children"]
64
76
  else
65
77
  # The parent definition wasn't loaded previously, fallback to guessing based on depth.
@@ -73,12 +85,13 @@ module Addressing
73
85
 
74
86
  # Loads the subdivision definitions for the provided parents.
75
87
  def load_definitions(parents)
88
+ @definitions ||= {}
76
89
  group = build_group(parents.dup)
77
- if @@definitions.key?(group)
78
- return @@definitions[group]
90
+ if @definitions.key?(group)
91
+ return @definitions[group]
79
92
  end
80
93
 
81
- @@definitions[group] = {}
94
+ @definitions[group] = {}
82
95
 
83
96
  # If there are predefined subdivisions at this level, try to load them.
84
97
  if has_data(parents)
@@ -86,12 +99,12 @@ module Addressing
86
99
 
87
100
  if File.exist?(filename)
88
101
  raw_definition = File.read(filename)
89
- @@definitions[group] = JSON.parse(raw_definition)
90
- @@definitions[group] = process_definitions(@@definitions[group])
102
+ @definitions[group] = JSON.parse(raw_definition)
103
+ @definitions[group] = process_definitions(@definitions[group])
91
104
  end
92
105
  end
93
106
 
94
- @@definitions[group]
107
+ @definitions[group]
95
108
  end
96
109
 
97
110
  # Processes the loaded definitions.
@@ -164,13 +177,14 @@ module Addressing
164
177
  grandparents = parents.dup
165
178
  parent_id = grandparents.pop
166
179
  parent_group = build_group(grandparents.dup)
180
+ @parents ||= {}
167
181
 
168
- if !@@parents.dig(parent_group, parent_id)
169
- @@parents[parent_group] ||= {}
170
- @@parents[parent_group][parent_id] = get(parent_id, grandparents)
182
+ if !@parents.dig(parent_group, parent_id)
183
+ @parents[parent_group] ||= {}
184
+ @parents[parent_group][parent_id] = get(parent_id, grandparents)
171
185
  end
172
186
 
173
- definition["parent"] = @@parents[parent_group][parent_id]
187
+ definition["parent"] = @parents[parent_group][parent_id]
174
188
  end
175
189
 
176
190
  # Prepare children.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Addressing
4
- VERSION = "0.7.0"
4
+ VERSION = "1.1.0"
5
5
  end
data/lib/addressing.rb CHANGED
@@ -6,6 +6,7 @@ require "digest"
6
6
  require "json"
7
7
 
8
8
  # modules
9
+ require "addressing/exceptions"
9
10
  require "addressing/enum"
10
11
  require "addressing/address"
11
12
  require "addressing/address_field"
@@ -23,14 +24,6 @@ require "addressing/postal_label_formatter"
23
24
  require "addressing/subdivision"
24
25
  require "addressing/version"
25
26
 
26
- module Addressing
27
- class Error < StandardError; end
28
-
29
- class UnknownCountryError < Error; end
30
-
31
- class UnknownLocaleError < Error; end
32
- end
33
-
34
27
  if defined?(ActiveSupport.on_load)
35
28
  ActiveSupport.on_load(:active_record) do
36
29
  require "addressing/model"
metadata CHANGED
@@ -1,16 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: addressing
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robin van der Vleuten
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-01-30 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
- description:
14
12
  email: robinvdvleuten@gmail.com
15
13
  executables: []
16
14
  extensions: []
@@ -712,6 +710,7 @@ files:
712
710
  - lib/addressing/default_formatter.rb
713
711
  - lib/addressing/dependent_locality_type.rb
714
712
  - lib/addressing/enum.rb
713
+ - lib/addressing/exceptions.rb
715
714
  - lib/addressing/field_override.rb
716
715
  - lib/addressing/lazy_subdivisions.rb
717
716
  - lib/addressing/locale.rb
@@ -724,8 +723,9 @@ files:
724
723
  homepage: https://github.com/robinvdvleuten/addressing
725
724
  licenses:
726
725
  - MIT
727
- metadata: {}
728
- post_install_message:
726
+ metadata:
727
+ bug_tracker_uri: https://github.com/robinvdvleuten/addressing/issues
728
+ changelog_uri: https://github.com/robinvdvleuten/addressing/blob/main/CHANGELOG.md
729
729
  rdoc_options: []
730
730
  require_paths:
731
731
  - lib
@@ -733,15 +733,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
733
733
  requirements:
734
734
  - - ">="
735
735
  - !ruby/object:Gem::Version
736
- version: '3.0'
736
+ version: '3.1'
737
737
  required_rubygems_version: !ruby/object:Gem::Requirement
738
738
  requirements:
739
739
  - - ">="
740
740
  - !ruby/object:Gem::Version
741
741
  version: '0'
742
742
  requirements: []
743
- rubygems_version: 3.4.10
744
- signing_key:
743
+ rubygems_version: 3.6.8
745
744
  specification_version: 4
746
745
  summary: Addressing library powered by CLDR and Google's address data
747
746
  test_files: []