Module: ConcernsOnRails::Support::AddressData
- Defined in:
- lib/concerns_on_rails/support/address_data.rb
Overview
Reference data + lookups for Models::Addressable. Kept here (mirroring ColumnGuard / RandomValue) so the concern itself stays lean and so the large constant tables live as plain literals rather than RuboCop-flagged blocks. All lookups are case-insensitive and string-safe.
Scope is format/structure only — this validates shape (a well-formed postal code, a real ISO country code), never real-world deliverability.
Constant Summary collapse
- COUNTRY_DATA =
Source of truth for ISO 3166-1: alpha-2 => [English name, alpha-3]. ISO_COUNTRY_CODES and the name/alpha-3 lookups below are all derived from this, so the three never drift apart. Each value is a [name, alpha-3] pair (a tuple), not a list of words. rubocop:disable Style/WordArray
{ "AD" => ["Andorra", "AND"], "AE" => ["United Arab Emirates", "ARE"], "AF" => ["Afghanistan", "AFG"], "AG" => ["Antigua and Barbuda", "ATG"], "AI" => ["Anguilla", "AIA"], "AL" => ["Albania", "ALB"], "AM" => ["Armenia", "ARM"], "AO" => ["Angola", "AGO"], "AQ" => ["Antarctica", "ATA"], "AR" => ["Argentina", "ARG"], "AS" => ["American Samoa", "ASM"], "AT" => ["Austria", "AUT"], "AU" => ["Australia", "AUS"], "AW" => ["Aruba", "ABW"], "AX" => ["Åland Islands", "ALA"], "AZ" => ["Azerbaijan", "AZE"], "BA" => ["Bosnia and Herzegovina", "BIH"], "BB" => ["Barbados", "BRB"], "BD" => ["Bangladesh", "BGD"], "BE" => ["Belgium", "BEL"], "BF" => ["Burkina Faso", "BFA"], "BG" => ["Bulgaria", "BGR"], "BH" => ["Bahrain", "BHR"], "BI" => ["Burundi", "BDI"], "BJ" => ["Benin", "BEN"], "BL" => ["Saint Barthélemy", "BLM"], "BM" => ["Bermuda", "BMU"], "BN" => ["Brunei", "BRN"], "BO" => ["Bolivia", "BOL"], "BQ" => ["Caribbean Netherlands", "BES"], "BR" => ["Brazil", "BRA"], "BS" => ["Bahamas", "BHS"], "BT" => ["Bhutan", "BTN"], "BV" => ["Bouvet Island", "BVT"], "BW" => ["Botswana", "BWA"], "BY" => ["Belarus", "BLR"], "BZ" => ["Belize", "BLZ"], "CA" => ["Canada", "CAN"], "CC" => ["Cocos (Keeling) Islands", "CCK"], "CD" => ["Democratic Republic of the Congo", "COD"], "CF" => ["Central African Republic", "CAF"], "CG" => ["Republic of the Congo", "COG"], "CH" => ["Switzerland", "CHE"], "CI" => ["Côte d'Ivoire", "CIV"], "CK" => ["Cook Islands", "COK"], "CL" => ["Chile", "CHL"], "CM" => ["Cameroon", "CMR"], "CN" => ["China", "CHN"], "CO" => ["Colombia", "COL"], "CR" => ["Costa Rica", "CRI"], "CU" => ["Cuba", "CUB"], "CV" => ["Cape Verde", "CPV"], "CW" => ["Curaçao", "CUW"], "CX" => ["Christmas Island", "CXR"], "CY" => ["Cyprus", "CYP"], "CZ" => ["Czechia", "CZE"], "DE" => ["Germany", "DEU"], "DJ" => ["Djibouti", "DJI"], "DK" => ["Denmark", "DNK"], "DM" => ["Dominica", "DMA"], "DO" => ["Dominican Republic", "DOM"], "DZ" => ["Algeria", "DZA"], "EC" => ["Ecuador", "ECU"], "EE" => ["Estonia", "EST"], "EG" => ["Egypt", "EGY"], "EH" => ["Western Sahara", "ESH"], "ER" => ["Eritrea", "ERI"], "ES" => ["Spain", "ESP"], "ET" => ["Ethiopia", "ETH"], "FI" => ["Finland", "FIN"], "FJ" => ["Fiji", "FJI"], "FK" => ["Falkland Islands", "FLK"], "FM" => ["Micronesia", "FSM"], "FO" => ["Faroe Islands", "FRO"], "FR" => ["France", "FRA"], "GA" => ["Gabon", "GAB"], "GB" => ["United Kingdom", "GBR"], "GD" => ["Grenada", "GRD"], "GE" => ["Georgia", "GEO"], "GF" => ["French Guiana", "GUF"], "GG" => ["Guernsey", "GGY"], "GH" => ["Ghana", "GHA"], "GI" => ["Gibraltar", "GIB"], "GL" => ["Greenland", "GRL"], "GM" => ["Gambia", "GMB"], "GN" => ["Guinea", "GIN"], "GP" => ["Guadeloupe", "GLP"], "GQ" => ["Equatorial Guinea", "GNQ"], "GR" => ["Greece", "GRC"], "GS" => ["South Georgia and the South Sandwich Islands", "SGS"], "GT" => ["Guatemala", "GTM"], "GU" => ["Guam", "GUM"], "GW" => ["Guinea-Bissau", "GNB"], "GY" => ["Guyana", "GUY"], "HK" => ["Hong Kong", "HKG"], "HM" => ["Heard Island and McDonald Islands", "HMD"], "HN" => ["Honduras", "HND"], "HR" => ["Croatia", "HRV"], "HT" => ["Haiti", "HTI"], "HU" => ["Hungary", "HUN"], "ID" => ["Indonesia", "IDN"], "IE" => ["Ireland", "IRL"], "IL" => ["Israel", "ISR"], "IM" => ["Isle of Man", "IMN"], "IN" => ["India", "IND"], "IO" => ["British Indian Ocean Territory", "IOT"], "IQ" => ["Iraq", "IRQ"], "IR" => ["Iran", "IRN"], "IS" => ["Iceland", "ISL"], "IT" => ["Italy", "ITA"], "JE" => ["Jersey", "JEY"], "JM" => ["Jamaica", "JAM"], "JO" => ["Jordan", "JOR"], "JP" => ["Japan", "JPN"], "KE" => ["Kenya", "KEN"], "KG" => ["Kyrgyzstan", "KGZ"], "KH" => ["Cambodia", "KHM"], "KI" => ["Kiribati", "KIR"], "KM" => ["Comoros", "COM"], "KN" => ["Saint Kitts and Nevis", "KNA"], "KP" => ["North Korea", "PRK"], "KR" => ["South Korea", "KOR"], "KW" => ["Kuwait", "KWT"], "KY" => ["Cayman Islands", "CYM"], "KZ" => ["Kazakhstan", "KAZ"], "LA" => ["Laos", "LAO"], "LB" => ["Lebanon", "LBN"], "LC" => ["Saint Lucia", "LCA"], "LI" => ["Liechtenstein", "LIE"], "LK" => ["Sri Lanka", "LKA"], "LR" => ["Liberia", "LBR"], "LS" => ["Lesotho", "LSO"], "LT" => ["Lithuania", "LTU"], "LU" => ["Luxembourg", "LUX"], "LV" => ["Latvia", "LVA"], "LY" => ["Libya", "LBY"], "MA" => ["Morocco", "MAR"], "MC" => ["Monaco", "MCO"], "MD" => ["Moldova", "MDA"], "ME" => ["Montenegro", "MNE"], "MF" => ["Saint Martin", "MAF"], "MG" => ["Madagascar", "MDG"], "MH" => ["Marshall Islands", "MHL"], "MK" => ["North Macedonia", "MKD"], "ML" => ["Mali", "MLI"], "MM" => ["Myanmar", "MMR"], "MN" => ["Mongolia", "MNG"], "MO" => ["Macao", "MAC"], "MP" => ["Northern Mariana Islands", "MNP"], "MQ" => ["Martinique", "MTQ"], "MR" => ["Mauritania", "MRT"], "MS" => ["Montserrat", "MSR"], "MT" => ["Malta", "MLT"], "MU" => ["Mauritius", "MUS"], "MV" => ["Maldives", "MDV"], "MW" => ["Malawi", "MWI"], "MX" => ["Mexico", "MEX"], "MY" => ["Malaysia", "MYS"], "MZ" => ["Mozambique", "MOZ"], "NA" => ["Namibia", "NAM"], "NC" => ["New Caledonia", "NCL"], "NE" => ["Niger", "NER"], "NF" => ["Norfolk Island", "NFK"], "NG" => ["Nigeria", "NGA"], "NI" => ["Nicaragua", "NIC"], "NL" => ["Netherlands", "NLD"], "NO" => ["Norway", "NOR"], "NP" => ["Nepal", "NPL"], "NR" => ["Nauru", "NRU"], "NU" => ["Niue", "NIU"], "NZ" => ["New Zealand", "NZL"], "OM" => ["Oman", "OMN"], "PA" => ["Panama", "PAN"], "PE" => ["Peru", "PER"], "PF" => ["French Polynesia", "PYF"], "PG" => ["Papua New Guinea", "PNG"], "PH" => ["Philippines", "PHL"], "PK" => ["Pakistan", "PAK"], "PL" => ["Poland", "POL"], "PM" => ["Saint Pierre and Miquelon", "SPM"], "PN" => ["Pitcairn Islands", "PCN"], "PR" => ["Puerto Rico", "PRI"], "PS" => ["Palestine", "PSE"], "PT" => ["Portugal", "PRT"], "PW" => ["Palau", "PLW"], "PY" => ["Paraguay", "PRY"], "QA" => ["Qatar", "QAT"], "RE" => ["Réunion", "REU"], "RO" => ["Romania", "ROU"], "RS" => ["Serbia", "SRB"], "RU" => ["Russia", "RUS"], "RW" => ["Rwanda", "RWA"], "SA" => ["Saudi Arabia", "SAU"], "SB" => ["Solomon Islands", "SLB"], "SC" => ["Seychelles", "SYC"], "SD" => ["Sudan", "SDN"], "SE" => ["Sweden", "SWE"], "SG" => ["Singapore", "SGP"], "SH" => ["Saint Helena", "SHN"], "SI" => ["Slovenia", "SVN"], "SJ" => ["Svalbard and Jan Mayen", "SJM"], "SK" => ["Slovakia", "SVK"], "SL" => ["Sierra Leone", "SLE"], "SM" => ["San Marino", "SMR"], "SN" => ["Senegal", "SEN"], "SO" => ["Somalia", "SOM"], "SR" => ["Suriname", "SUR"], "SS" => ["South Sudan", "SSD"], "ST" => ["São Tomé and Príncipe", "STP"], "SV" => ["El Salvador", "SLV"], "SX" => ["Sint Maarten", "SXM"], "SY" => ["Syria", "SYR"], "SZ" => ["Eswatini", "SWZ"], "TC" => ["Turks and Caicos Islands", "TCA"], "TD" => ["Chad", "TCD"], "TF" => ["French Southern Territories", "ATF"], "TG" => ["Togo", "TGO"], "TH" => ["Thailand", "THA"], "TJ" => ["Tajikistan", "TJK"], "TK" => ["Tokelau", "TKL"], "TL" => ["Timor-Leste", "TLS"], "TM" => ["Turkmenistan", "TKM"], "TN" => ["Tunisia", "TUN"], "TO" => ["Tonga", "TON"], "TR" => ["Turkey", "TUR"], "TT" => ["Trinidad and Tobago", "TTO"], "TV" => ["Tuvalu", "TUV"], "TW" => ["Taiwan", "TWN"], "TZ" => ["Tanzania", "TZA"], "UA" => ["Ukraine", "UKR"], "UG" => ["Uganda", "UGA"], "UM" => ["United States Minor Outlying Islands", "UMI"], "US" => ["United States", "USA"], "UY" => ["Uruguay", "URY"], "UZ" => ["Uzbekistan", "UZB"], "VA" => ["Vatican City", "VAT"], "VC" => ["Saint Vincent and the Grenadines", "VCT"], "VE" => ["Venezuela", "VEN"], "VG" => ["British Virgin Islands", "VGB"], "VI" => ["U.S. Virgin Islands", "VIR"], "VN" => ["Vietnam", "VNM"], "VU" => ["Vanuatu", "VUT"], "WF" => ["Wallis and Futuna", "WLF"], "WS" => ["Samoa", "WSM"], "YE" => ["Yemen", "YEM"], "YT" => ["Mayotte", "MYT"], "ZA" => ["South Africa", "ZAF"], "ZM" => ["Zambia", "ZMB"], "ZW" => ["Zimbabwe", "ZWE"] }.freeze
- ISO_COUNTRY_CODES =
ISO 3166-1 alpha-2 country codes (derived from COUNTRY_DATA).
Set.new(COUNTRY_DATA.keys).freeze
- NAME_TO_ALPHA2 =
Downcased English country name => alpha-2, and alpha-3 => alpha-2.
COUNTRY_DATA.to_h { |code, (name, _a3)| [name.downcase, code] }.freeze
- ALPHA3_TO_ALPHA2 =
COUNTRY_DATA.to_h { |code, (_name, a3)| [a3, code] }.freeze
- POSTAL_FORMATS =
Per-country postal-code patterns (matched against the normalized, upcased value). ‘:default` is a permissive fallback for everything else.
{ "US" => /\A\d{5}(-\d{4})?\z/, "CA" => /\A[A-Z]\d[A-Z] ?\d[A-Z]\d\z/, "GB" => /\A[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}\z/, "AU" => /\A\d{4}\z/, "DE" => /\A\d{5}\z/, "FR" => /\A\d{5}\z/, default: /\A[A-Z0-9][A-Z0-9 -]{1,8}[A-Z0-9]\z/ }.freeze
- US_STATES =
USPS state / territory abbreviations.
Set.new(%w[ AL AK AZ AR CA CO CT DE FL GA HI ID IL IN IA KS KY LA ME MD MA MI MN MS MO MT NE NV NH NJ NM NY NC ND OH OK OR PA RI SC SD TN TX UT VT VA WA WV WI WY DC AS GU MP PR VI ]).freeze
- CA_PROVINCES =
Canadian province / territory codes.
Set.new(%w[AB BC MB NB NL NS NT NU ON PE QC SK YT]).freeze
Class Method Summary collapse
-
.normalize_country_code(value) ⇒ Object
Canonicalize a country value to its ISO 3166-1 alpha-2 code: an existing alpha-2 is upcased; a 3-letter alpha-3 (e.g. “USA”) and a recognized English name (e.g. “Canada”) map to the alpha-2.
-
.normalize_postal(country, value) ⇒ Object
Squish + upcase a postal code, adding canonical spacing for CA (‘A1A1A1` -> `A1A 1A1`).
-
.postal_format_for(country) ⇒ Object
Regexp to validate a postal code for the given country (falls back to the permissive ‘:default` pattern for unmapped countries).
-
.valid_country?(code) ⇒ Boolean
True when ‘code` is a known ISO 3166-1 alpha-2 country code.
-
.valid_state?(country, code) ⇒ Boolean
Validate a state/region against US / CA sets.
Class Method Details
.normalize_country_code(value) ⇒ Object
Canonicalize a country value to its ISO 3166-1 alpha-2 code: an existing alpha-2 is upcased; a 3-letter alpha-3 (e.g. “USA”) and a recognized English name (e.g. “Canada”) map to the alpha-2. Unrecognized values are returned unchanged, and non-strings pass through.
341 342 343 344 345 346 347 348 349 350 351 352 |
# File 'lib/concerns_on_rails/support/address_data.rb', line 341 def normalize_country_code(value) return value unless value.is_a?(String) trimmed = value.strip.squish return value if trimmed.empty? upper = trimmed.upcase return upper if ISO_COUNTRY_CODES.include?(upper) return ALPHA3_TO_ALPHA2[upper] if ALPHA3_TO_ALPHA2.key?(upper) NAME_TO_ALPHA2[trimmed.downcase] || value end |
.normalize_postal(country, value) ⇒ Object
Squish + upcase a postal code, adding canonical spacing for CA (‘A1A1A1` -> `A1A 1A1`). Non-strings pass through unchanged.
327 328 329 330 331 332 333 334 335 |
# File 'lib/concerns_on_rails/support/address_data.rb', line 327 def normalize_postal(country, value) return value unless value.is_a?(String) normalized = value.strip.squish.upcase return normalized unless country.to_s.upcase == "CA" compact = normalized.delete(" ") compact.match?(/\A[A-Z]\d[A-Z]\d[A-Z]\d\z/) ? "#{compact[0, 3]} #{compact[3, 3]}" : normalized end |
.postal_format_for(country) ⇒ Object
Regexp to validate a postal code for the given country (falls back to the permissive ‘:default` pattern for unmapped countries).
309 310 311 |
# File 'lib/concerns_on_rails/support/address_data.rb', line 309 def postal_format_for(country) POSTAL_FORMATS[country.to_s.upcase] || POSTAL_FORMATS[:default] end |
.valid_country?(code) ⇒ Boolean
True when ‘code` is a known ISO 3166-1 alpha-2 country code.
301 302 303 304 305 |
# File 'lib/concerns_on_rails/support/address_data.rb', line 301 def valid_country?(code) return false unless code.is_a?(String) ISO_COUNTRY_CODES.include?(code.upcase) end |
.valid_state?(country, code) ⇒ Boolean
Validate a state/region against US / CA sets. Returns true for any other country (we only know those two), so callers needn’t special-case.
315 316 317 318 319 320 321 322 323 |
# File 'lib/concerns_on_rails/support/address_data.rb', line 315 def valid_state?(country, code) return true unless code.is_a?(String) case country.to_s.upcase when "US" then US_STATES.include?(code.upcase) when "CA" then CA_PROVINCES.include?(code.upcase) else true end end |