Module: Worldwide::Zip
Instance Method Summary collapse
-
#find_country(country_code: nil, zip:, min_confidence: 0) ⇒ Region
Suggest a country for a given postal code.
-
#gb_style?(country_code:) ⇒ Boolean
The United Kingdom has an unusual style of postcode.
- #gb_style_outcode(country_code:, input:) ⇒ Object
-
#normalize(country_code:, zip:, allow_autofill: true, strip_extraneous_characters: false) ⇒ String
Normalizes the postal code into the format expected by the national postal authority.
-
#numeric_only_zip?(country_code:) ⇒ Boolean
We want to show numeric keypad on mobile view for countries with only numeric postal codes Spaces or dashes are allowed.
-
#outcode(country_code:, zip:) ⇒ Object
Returns the “outcode” (first portion) of a postcode for a country that uses the UK style.
-
#pure_numeric_only_zip?(country_code:) ⇒ Boolean
We want to show numeric keypad on mobile view for countries with only numeric postal codes Spaces or dashes aren’t allowed.
- #strip_optional_country_prefix(country_code:, zip:) ⇒ Object
Instance Method Details
#find_country(country_code: nil, zip:, min_confidence: 0) ⇒ Region
Suggest a country for a given postal code.
Some countries have an ambiguous dual state. For example, some consider Jersey to be a top-level country in its own right, while others consider it to be part of the United Kingdom. This leads to frustrated buyers receiving a validation error when they enter their address and the postal code isn’t valid for the selected “country”.
Also, in some cases, users are simply confused, and use a postal code that is obviously inappropriate for the selected country. Some examples:
- entering a postal code for Georgia, United States, but selecting the country Georgia
- using a USA FPO address, but selecting the non-US physical location of the US base as the country
This method attempts to heuristically suggest a country in some common cases where we see a lot of confusion. There’s no way to solve this problem for all cases, and we will often not have a suggestion (in which case this method will return ‘nil`).
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/worldwide/zip.rb', line 52 def find_country(country_code: nil, zip:, min_confidence: 0) return nil unless Util.present?(zip) country = Worldwide.region(code: country_code) unless country_code.nil? return country if country&.valid_zip?(zip) adjusted_zip = zip.strip.upcase # Try to match based on the alleged country suggestion, confidence = find_country_using_alleged_country(country_code, adjusted_zip) return suggestion unless suggestion.nil? || confidence.nil? || confidence < min_confidence # If our postal code is wholly numeric, we can't make an intelligent suggestion without an alleged country. return nil unless adjusted_zip.match?(/[A-Z]/) # Try a broader-ranging match without considering the alleged country # We'll see if we have only a single suggestion and, if so, return it. # In cases where there's more than one possible match, we'll return nil. suggestions = find_country_using_zip_alone(adjusted_zip) suggestion = suggestions.first[0] unless Util.blank?(suggestions) confidence = suggestions.first[1] unless Util.blank?(suggestions) return suggestion if suggestions.length == 1 && confidence && confidence >= min_confidence nil end |
#gb_style?(country_code:) ⇒ Boolean
The United Kingdom has an unusual style of postcode. It consists of two halves (outcode and incode), separated by a space. The incode is always 3 characters, with 1 digit followed by 2 letters. The outcode varies from 2 to 4 characters, following certain patterns. A handful of other countries also allocate their codes within the UK “namespace”.
28 29 30 |
# File 'lib/worldwide/zip.rb', line 28 def gb_style?(country_code:) GB_STYLE_ZIP_COUNTRIES.include?(country_code.to_s.upcase) end |
#gb_style_outcode(country_code:, input:) ⇒ Object
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'lib/worldwide/zip.rb', line 215 def gb_style_outcode(country_code:, input:) normalized = input m = /^([A-Z]{1,2})/.match(normalized) return normalized if m.nil? area = m[1] return normalized if area.nil? # Let's get the first, second and third characters following the area first = normalized[area.length] second = normalized[area.length + 1] third = normalized[area.length + 2] candidate = case area when "E", "EC", "N", "NW", "SE", "SW", "W", "WC" # These London districts may include a single-letter suffix as part of the district # In all such cases, the district is one digit + one letter if /[A-Z]/.match?(second) # This must be a district suffix, so disctrict consists of first + second "#{area}#{first}#{second}".strip elsif /[A-Z]/.match?(third) # Because third is a letter, second must be the sector, and the district must be a single digit "#{area}#{first}".strip else # Otherwise, we'll assume that we've got a two-digit district "#{area}#{first}#{second}".strip end else # The outcode is the area plus at most two digits following it if /[A-Z]/.match?(third) # Because third is a letter, second must be the sector, and the district must be a single digit "#{area}#{first}" else "#{area}#{first}#{second}".strip end end if /[0-9]/.match?(second) && !Worldwide::ExtantOutcodes.for_country(code: country_code).include?(candidate) # We've guessed a two-digit district that does not actually exist, so we'll assume a single-digit district. # For example, "GL71" can't be an outcode, so we'll assume the user meant outcode "GL7". "#{area}#{first}" else candidate end end |
#normalize(country_code:, zip:, allow_autofill: true, strip_extraneous_characters: false) ⇒ String
Normalizes the postal code into the format expected by the national postal authority.
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/worldwide/zip.rb', line 85 def normalize(country_code:, zip:, allow_autofill: true, strip_extraneous_characters: false) input = zip # preserve the original zip, in case we need to fall back to it return input if Util.blank?(country_code) country = Worldwide.region(code: country_code) return zip if country.nil? || NORMALIZATION_DISABLED_COUNTRIES.include?(country.iso_code) if allow_autofill autofill = country.autofill_zip return autofill if Util.present?(autofill) end return nil if zip.nil? # Convert to uppercase # Convert numeric and romaji full-width to half-width # Strip hyphens, dashes and the Japanese postcode marker zip = zip.upcase.tr("0-9a-zA-Z", "0-9a-zA-Z") .delete("〒\u058a\u05be\u1806\u1b60\u200b\u2010\u2011\u2012\u2013\u2014\u2015\u2053\u2e17\u2e3a\u2e3b\u2212\u30fb\u30fc\ufe58\ufe63\uff0d\uff65(),./_~-") zip = add_prefix_if_required(country_code: country_code, zip: zip) if strip_extraneous_characters zip = strip_extraneous_characters(zip: zip, country_code: country_code) end return normalize_for_gb(zip: zip) if gb_style?(country_code: country.iso_code) # Remove both normal-width and double-width spaces zip.delete!(" ") zip = replace_letters_and_numbers(country_code: country.iso_code, zip: zip) result = if "BD" == country.iso_code normalize_for_bd(zip: zip) elsif "FO" == country.iso_code normalize_for_fo(zip: zip) elsif "GH" == country.iso_code normalize_for_gh(zip: zip) elsif "HT" == country.iso_code normalize_for_ht(zip: zip) elsif "LK" == country.iso_code normalize_for_lk(zip: zip) elsif "MD" == country.iso_code normalize_for_md(zip: zip) elsif "MG" == country.iso_code normalize_for_mg(zip: zip) elsif "NG" == country.iso_code normalize_for_ng(zip: zip) elsif "SG" == country.iso_code normalize_for_sg(zip: zip) elsif "MA" == country.iso_code normalize_for_ma(zip: zip) elsif "XK" == country.iso_code normalize_for_xk(zip: zip) elsif "BR" == country.iso_code || "JP" == country.iso_code insert_spaces_and_hyphens_for_partial_code(country_code: country.iso_code, zip: zip) else insert_spaces_and_hyphens(country_code: country.iso_code, zip: zip) end if country.send(:valid_normalized_zip?, result) result elsif country.send(:valid_normalized_zip?, result, partial_match: true) result else input # fall back to the original input, because we don't seem to have generated anything sensible end end |
#numeric_only_zip?(country_code:) ⇒ Boolean
We want to show numeric keypad on mobile view for countries with only numeric postal codes Spaces or dashes are allowed.
10 11 12 |
# File 'lib/worldwide/zip.rb', line 10 def numeric_only_zip?(country_code:) NUMERIC_ONLY_ZIP_COUNTRIES.include?(country_code.to_s.upcase) end |
#outcode(country_code:, zip:) ⇒ Object
Returns the “outcode” (first portion) of a postcode for a country that uses the UK style. Returns the “forward sortation area” (first portion) of a postal code for Canada. Returns the “routing key” (first portion) of a postal code for Ireland. Otherwise, returns the full zip.
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/worldwide/zip.rb', line 157 def outcode(country_code:, zip:) @split_code_countries ||= Set.new(GB_STYLE_ZIP_COUNTRIES).add("CA").add("IE") return zip unless @split_code_countries.include?(country_code.to_s.upcase) normalized_input = normalize(country_code: country_code, zip: zip).upcase normalized_country_code = country_code.to_s.upcase if Worldwide.region(code: country_code).valid_zip?(normalized_input) # We successfully normalized the postcode, so we can split on the space to determine the outcode normalized_input&.split(" ")&.first else # We have either an invalid or an incomplete postcode. Let's try some fancy heuristics case country_code.to_s.upcase when "CA", "IE" # the FSA / routing code is the first 3 characters normalized_input[0..2] when "GB", "GG", "IM", "JE" gb_style_outcode(country_code: normalized_country_code, input: normalized_input) when "GI" "GX11" else normalized_input end end end |
#pure_numeric_only_zip?(country_code:) ⇒ Boolean
We want to show numeric keypad on mobile view for countries with only numeric postal codes Spaces or dashes aren’t allowed.
17 18 19 20 21 |
# File 'lib/worldwide/zip.rb', line 17 def pure_numeric_only_zip?(country_code:) return false if SPACES_AND_HYPHENS.key?(country_code.to_s.upcase.to_sym) numeric_only_zip?(country_code: country_code) end |
#strip_optional_country_prefix(country_code:, zip:) ⇒ Object
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/worldwide/zip.rb', line 184 def strip_optional_country_prefix(country_code:, zip:) return zip if Util.blank?(zip) unless OPTIONAL_PREFIX_COUNTRIES.include?(country_code&.to_sym) return zip end vehicle_code = OPTIONAL_PREFIX_COUNTRIES[country_code&.to_sym] stripped = zip.strip upcased = stripped.upcase if upcased.start_with?(country_code&.to_s) stripped = stripped[country_code&.to_s&.length..-1] elsif upcased.start_with?(vehicle_code) stripped = stripped[vehicle_code.length..-1] end if stripped.start_with?("-") stripped = stripped[1..-1] end m = stripped.match(/^\d/) if m.nil? zip else stripped end end |