Handle international addresses in Ruby
You're building an e-commerce site, and a customer from Japan reaches checkout. Your form asks for a "State" and a "ZIP code."
Japan does not use that shape.
Or maybe you're printing shipping labels and discover that the USPS wants certain fields in ALL CAPS for automated sorting, but you have no idea which fields or why.
International address handling gets messy fast.
The problem nobody warns you about
A checkout form has to deal with details like these:
- The UK calls it a "postcode." Americans call it a "ZIP code." Canadians call it a "postal code."
- Japanese addresses go from largest to smallest (the complete opposite of Western addresses)
- Some countries don't have postal codes at all
- Ireland uses "County" but the US uses "State" and Canada uses "Province" (or sometimes "Territory")
- For automated mail sorting, you need to uppercase specific fields in specific countries
You can spend weeks researching postal systems across hundreds of countries. I would rather use a library that already packages that data.
That is why I built Addressing.
What it does
Addressing is a Ruby gem for postal address details across countries:
- 250+ country definitions with translations powered by CLDR data
- 200+ address formats, so you know which fields to show and in what order
- subdivisions for 60 countries: states, provinces, prefectures, and whatever they call them locally
- formatting in HTML and plain text
- Active Record validation
Your first address
A simple address looks like this:
rubyaddress = Addressing::Address.new(country_code: "US",administrative_area: "CA",locality: "Mountain View",address_line1: "1600 Amphitheatre Parkway",given_name: "Sundar",family_name: "Pichai")formatter = Addressing::DefaultFormatter.newputs formatter.format(address)
That outputs HTML with the fields in the right order for the US, plus semantic CSS classes. The country name is localized automatically.
You do not have to guess the field order or hardcode it into the form.
Real-world examples
Building a checkout form that works everywhere
Different countries need different fields. Nobody in Japan needs a "State" dropdown, but they absolutely need a "Prefecture" field:
ruby# Get the address format for Japanformat = Addressing::AddressFormat.get('JP')# Build your form dynamicallyformat.used_fields.each do |field|puts "#{format.label_for(field)}: [input field]"end
Your Japanese customers see the fields they expect. Your UK customers don't see irrelevant fields. That is the important part: the form matches the country.
Rails validation
Your model needs to accept addresses from anywhere:
rubyclass Order < ApplicationRecordvalidates_address_formatend
Postal codes get validated against country-specific patterns. Required fields get enforced. Customers can complete checkout without fighting a form built for another country.
Printing shipping labels
You're printing labels for international shipments. Some countries require specific fields in ALL CAPS for automated sorting:
rubyaddress = Addressing::Address.new(country_code: "US",administrative_area: "CA",locality: "Mountain View",address_line1: "1098 Alta Ave")formatter = Addressing::PostalLabelFormatter.new(origin_country: "FR")puts formatter.format(address)# Output:# 1098 Alta Ave# MOUNTAIN VIEW, CA 94043# ÉTATS-UNIS - UNITED STATES
Notice what happened:
- City and state got uppercased because USPS expects that.
- The country name appears in both French and English, following the Universal Postal Union recommendation.
- The label is formatted for international mail from France.
The formatter handles the postal label rules for you.
Country selectors in any language
Need a dropdown in the user's language?
ruby# Get all countries in Japanesecountries = Addressing::Country.list('ja-JP')# => { "JP" => "日本", "US" => "アメリカ合衆国", ... }# Or get detailed infobrazil = Addressing::Country.get('BR')puts brazil.currency_code # "BRL"puts brazil.timezones # All timezones Brazil spans
French users see "États-Unis." Japanese users see "アメリカ合衆国." The labels come from the locale data.
Cascading location dropdowns
For forms where selecting a country shows states, then cities:
ruby# Get all states in Brazilstates = Addressing::Subdivision.all(['BR'])# User selected Ceará? Get municipalitiesmunicipalities = Addressing::Subdivision.all(['BR', 'CE'])# Traverse the hierarchystates.each do |state|state.children.each do |municipality|puts "#{state.name} - #{municipality.name}"endend
The data is hierarchical and translated, so the form can follow the selected country.
Why I would not roll this by hand
The gem builds on Google's Address Data Service and the CLDR project. One bundle install gives you the country data, labels, formats, and subdivisions without turning your checkout into a postal research project.
It also keeps the odd cases close to the code. Singapore uses six-digit postal codes. Russia uses Cyrillic addresses. Some places have no postal codes at all. Those are not details I want scattered through view helpers.
Address formats change over time too. Countries split, subdivisions get renamed, and postal rules move. Tracking CLDR updates in one library is much easier than rediscovering those changes in every app.
This Ruby version ports the PHP addressing library from CommerceGuys, which has already done this work for major e-commerce platforms.
When to use this
Use it when addresses are more than a text box:
- checkout flows where wrong addresses mean failed deliveries
- booking systems that collect international customer details
- multi-tenant SaaS where customers operate in different countries
- shipping tools that need postal labels to follow local rules
Getting started
Check out Addressing on GitHub for installation and complete documentation.
Add it to your Gemfile, then wire up the validators and formatters where addresses enter or leave your application.
Wrong addresses cost real money through failed shipments and frustrated customers. This gem turns a lot of postal research into an API you can call directly.
Next time you are about to add a "State" field to an international form, check whether the selected country even has one.