Open-source project
countries/countries avatar
countries/countries

countries: ISO 3166, 4217 and E.164 data in Ruby objects

All sorts of useful information about every country packaged as convenient little country objects. It includes data from ISO 3166 (countries and states/subdivisions ), ISO 4217 (currency), and E.164 (phone numbers).

2,363 stars682 forksRubyMIT

At a glance

What is it?
countries is a MIT-licensed Ruby gem that wraps ISO 3166-1 countries, ISO 3166-2 subdivisions, ISO 4217 currency and E.164 telephone data as objects with a large attribute surface. Two details decide whether it fits your form code: the deprecated states method returns every subdivision regardless of type, and latitude and longitude are strings.
Who is it for?
Adopt countries if you need ISO identifiers, translated country names, subdivision codes and telephone prefixes in a Ruby application and you would rather not maintain your own reference table. Do not adopt it if you are storing coordinates in a numeric column, because latitude and longitude come back as strings and the latitude_dec and longitude_dec accessors were removed in 5.0, so every consumer has to call to_f.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 24 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The states method returns every subdivision, not just states

One deprecated method in this gem is a live footgun, and the README says so in a comment that most readers skim.

ruby
c.subdivisions # => {"CO" => {"name" => "Colorado", "names" => "Colorado"}, ... }
c.subdivision_types # => ["state", "outlying_area", "district"]
c.subdivisions_of_types(['state']) # => {"CO" => {"name" => "Colorado", "names" => "Colorado"}, ... }
c.humanized_subdivision_types # => ["State", "Outlying area", "District"]

# This is now deprecated. #states is an alias of #subdivisions and returns all subdivisions regardless of type
c.states # => {"CO" => {"name" => "Colorado", "names" => "Colorado"}, ... }

The gem already has the correct method. subdivisions_of_types takes an array of type strings and filters, so subdivisions_of_types(['state']) gives you states. The deprecated states alias does not filter, and it is described as returning all subdivisions regardless of type. Against the United States, whose subdivision_types include state, outlying_area and district, that means your dropdown contains fifty states plus the District of Columbia, plus outlying areas and districts, all under a method called states.

The failure mode is a form or a validation that quietly accepts a territory it should have rejected, or a jurisdiction list that includes entries a compliance system will not recognise. Nothing raises. The hash has the shape you expect and the keys are valid subdivision codes, because they are real subdivisions.

The deprecation notice is a comment in a code sample rather than a warning, and states is not marked with any runtime deprecation visible in the sample. So a codebase that adopted this gem years ago and was written against states will keep working indefinitely while being wrong. The upgrade path is mechanical once you know: replace states with subdivisions_of_types(['state']) and pin the type list to what you actually mean, because a country's subdivision vocabulary is defined by ISO 3166-2 and differs between countries.

The other subdivision methods are worth pairing with it. subdivision_names_with_codes('es') returns translated names alongside codes, code_with_translations gives a subdivision's code with every loaded locale, and find_subdivision_by_name resolves a subdivision by code or by name in any translation, returning a geo hash.

Latitude and longitude are strings, and the decimal accessors are gone

The location accessors return strings, and the convenience methods that would have given you numbers were removed.

ruby
c.latitude # => "37.09024"
c.longitude # => "-95.712891"

c.world_region # => "AMER"
c.region # => "Americas"
c.subregion # => "Northern America"

Those are quoted values in the README's own examples. A country object gives you a String for its coordinates, and the note below the example explains what happened: latitude_dec and longitude_dec were deprecated in release 4.2 and removed in 5.0, because those attributes had been redundant for several years, since the latitude and longitude fields have been switched decimal coordinates.

The reasoning is sound and the outcome is not. The fields were switched to hold decimal coordinates, at which point the _dec variants became identical to the base fields, so they were deprecated as redundant. But the base fields were left as strings, and the string-typed accessors were then removed as well. The result is that the gem has no way to return a coordinate as a number, and every consumer has to remember to call to_f.

That matters more than it looks. c.latitude * 2 raises, c.latitude + 1 concatenates, and a comparison of c.latitude.to_f against a threshold works only because the caller remembered. If you are writing a bounding-box filter, a nearest-office calculation or a value object, you are writing to_f at every boundary, and a developer who misses one gets either an exception or a silent lexicographic comparison, since string ordering on decimal values is wrong for negatives and for values with different digit counts.

The same pattern appears in the boundary box accessors, min_longitude, min_latitude, max_longitude and max_latitude, and in the bounds hash with its northeast and southwest or similar structure. Treat every numeric-looking accessor in this gem as a String until you have checked, and note that world_region returns a code such as AMER while region returns the human string Americas and subregion returns Northern America, so the region hierarchy is three fields with two different kinds of value in it.

Six kinds of name for one country, one of them mislabelled

The largest part of this gem's attribute surface is naming, and the reason there are so many name fields is a deliberate design decision recorded in the upgrade notes.

Release 4.2.0 introduced changes to name attributes and finders and deprecated several methods to resolve existing confusion regarding official ISO country names versus the common names that are commonly used. Release 5.0 removed those deprecated methods. So the field explosion is the fix for a real ambiguity, not an accident.

ruby
c.iso_long_name # => "The United States of America"
c.iso_short_name # => "United States of America"
c.iso_short_name_lower_case # => "United States of America (the)"
c.common_name # => "United States" (This is a shortcut for c.translations('en'))
c.unofficial_names # => ["United States of America", "Vereinigte Staaten von Amerika", "États-Unis", "Estados Unidos"]

Three of those are ISO fields, one is a shortcut into the translation table for English, and one is a catch-all. The third one deserves a second look. A field named iso_short_name_lower_case returns "United States of America (the)", which is not lower case and carries a trailing parenthetical article. The name describes a transformation the value does not have, and a developer who reaches for it expecting a normalised, comparison-safe string will get a string with an uppercase U and a bracketed suffix instead.

The translation side is fuller and behaves as you would expect. local_names returns the country's names in its own languages, so Belgium gives België, Belgique and Belgien, and local_name returns the first. translation accepts a string or a symbol locale and returns one name, while translations returns a symbol-keyed hash, so translations[:fr] gives the French name. At class level, ISO3166::Country.translations returns a hash for the default locale of en, ISO3166::Country.translations('de') returns names keyed by alpha2 code, and all_translated returns a flat list. nationality returns the demonym, American.

The practical advice is to decide which of the six you mean before you write the call, and to use common_name for display and iso_short_name for anything that has to match a standard.

The data lives in two submodule repositories, and the last release is from January

The data and the code are versioned separately, and the two version numbers you care about are not moving together.

The README states that the data used in this gem is also available as git submodules in the YAML and JSON forms, in the countries-data-yaml and countries-data-json repositories. So there are three repositories in play: this gem, and the two data repositories it consumes as submodules. The gem's release therefore bundles a specific snapshot of ISO data, and that snapshot is frozen at release time whether or not the upstream standards changed.

The dates here are the interesting part. The three most recent releases are v8.0.3 on 2025-07-11, v8.0.4 on 2025-08-30 and v8.1.0 on 2026-01-02. The last push to the repository was 2026-09-05. So the default branch has moved through eight months of work since the January release, and gem install countries gives you the January artefact.

For ISO 3166-1 country codes, that staleness is close to irrelevant. The set of sovereign states and their alpha2 and alpha3 codes changes on a scale of years. Subdivision data is more volatile than country data, since ISO 3166-2 revisions happen when a country reorganises its administrative divisions, and currency data under ISO 4217 changes when a currency is redenominated or a code is reassigned. Whether any of that changed in the eight months in question is not something the README lets you determine, which is exactly why the submodule arrangement matters: if you need to know whether a subdivision code you depend on is current, the data repository is the place to look, and it has its own history independent of the gem's.

The practical advice is to pin the gem version, treat the data as a snapshot rather than a live feed, and check the data repository directly when a specific code is load-bearing for you.

The top-level Country constant is a documented namespace hazard

The gem offers two ways to construct a country object, and the second one is a warning as much as a convenience.

The normal form is the namespaced class:

ruby
c = ISO3166::Country.new('US')

The shortcut form, Country.new(*alpha2*) or Country[*alpha2*], comes from an opt-in global helper, and enabling it is a one-line Gemfile change:

ruby
gem 'countries', require: 'countries/global'

The README's own caveat is in bold: This will conflict with any existing Country constant.

That is a real and unquantified risk in the environments this gem is most often used in. In a Rails application, a top-level Country constant is a plausible thing to already have, whether from an older model, a legacy table class, an admin interface or a previous gem that defined it and has since been removed. The conflict surfaces as a Ruby constant redefinition, which may be a warning or an error depending on load order, and the resulting failure is a class you did not expect receiving the calls you meant for the gem.

The two forms are otherwise equivalent, so the choice is purely about namespace collision risk against typing. Keeping ISO3166::Country is more verbose and never collides. The global helper reads better in a model that deals with nothing but countries, and that readability is what most people are buying when they enable it.

The same trade appears in the finder methods, which are dynamic and therefore untyped. find_country_by_iso_short_name, find_country_by_any_name, find_all_by and find_all_countries_by_region are generated from the attribute list, which the README points at as ISO3166::DEFAULT_COUNTRY_HASH. A typo in the attribute name is a NoMethodError at runtime rather than a static type error, and a typo in a locale symbol passed to translation silently returns a nil translation rather than raising. The attribute list is the API contract, and it is a runtime value rather than a declaration.

Search ignores case and accents, which helps and collides

One line in the README describes a behaviour that is both the gem's most useful feature and its most likely source of surprise.

Searches are case insensitive and ignore accents. The examples make it concrete: find_country_by_iso_short_name('italy') returns Italy, find_country_by_any_name('united states') returns the United States, and find_all_by(:translated_names, 'França') finds entries in the French translation table.

That normalisation is what makes the gem pleasant for user-facing search, and it is the reason a form field labelled country can accept italy, Italy, ITALY and Italie-adjacent input and still resolve. Without it, every one of those would be a miss.

The cost is that distinct stored names which differ only in accents become the same key, and the gem cannot tell you which one matched. Search for a name and you get a country, not the string that matched it. If your application displays what the user typed and stores a resolved code, you have two different strings for the same thing, and the round trip is not lossless. That is a design decision most applications want, and some, particularly ones that reconcile against an external authority list, do not.

The removed finders are the other half of this story. find_by_name, find_by_names, find_*_by_name and find_*_by_names were removed in 5.0, after being deprecated in 4.2.0 alongside the name attribute changes, because the unprefixed name lookups were exactly the ambiguity the release was resolving: which name, from which field, official or common. What replaced them is the find_country_by_<attribute> family, which forces you to name the attribute, and the find_all_by form that takes a symbol.

So on v8.1.0, a codebase still calling find_by_name does not get a deprecation warning at runtime. It gets a NoMethodError, which is the good outcome, because it is loud and it happens in your test suite rather than in production.

CodeQL, qlty, reek and rubocop on a reference-data gem

The badge row and the repository listing say more about how this project is maintained than any prose in the README.

The badges cover a gem version badge, a tests workflow, a CodeQL analysis workflow, and qlty maintainability and code coverage. The repository root backs them up: a .qlty directory, a .reek.yml, a .rubocop.yml, a .rspec, a Rakefile, a spec directory and a bin directory.

Reek is the unusual one. Rubocop measures style and correctness, rspec runs the tests, and CodeQL looks for security patterns. Reek is a code smell detector aimed at design, and it reports things like long parameter lists, feature envy, duplicated code and nil checks in the object model. Configuring it in a country data gem is a statement about what the code should be, not just how it should be formatted, and .reek.yml is the file where those tolerances are set. It is not a common choice for a reference-data library, and it is a good signal that the maintainers care about the object model staying legible.

Two files in that listing are worth flagging for a different reason. Gemfile.lock is committed, which is unusual for a gem repository, since a gem's own development lockfile has no effect on consumers and is normally ignored. It is harmless and it does make the test environment reproducible for maintainers, but it will show up as noise in a dependency audit. The other is UPGRADE.md, which the README links twice, once for the 4.2 and 5.x transition and once for the removed finder methods. A project that has broken its own API twice and shipped a document about it is a project with users who stayed.

The licence is MIT with a LICENSE file at the root, which is the least remarkable thing in the repository and the reason none of the above is a blocker. The last push was on 2026-09-05, so the maintenance is current even though the last release is not.

Editorial conclusion

Adopt countries if you need ISO identifiers, translated country names, subdivision codes and telephone prefixes in a Ruby application and you would rather not maintain your own reference table. Do not adopt it if you are storing coordinates in a numeric column, because latitude and longitude come back as strings and the latitude_dec and longitude_dec accessors were removed in 5.0, so every consumer has to call to_f. Do not use the states method for anything that assumes states, since it returns districts, outlying areas and dependencies alongside them. Verify four things. Pin the version, because v8.1.0 is dated 2026-01-02 while the last push was 2026-09-05, so an unversioned assumption about the current data is wrong. Check whether find_by_name still appears in your codebase, because that family of finders was removed in 5.0 and the replacement is the find_country_by_<attribute> dynamic form. Decide whether the top-level Country helper is worth the constant collision the README warns about, particularly in a Rails app. And test the accent-insensitive search against your real user input, since it will match names you did not type. The deciding fact is that this is a reference-data gem with a thoughtful attribute vocabulary, and the cost of adopting it is entirely in learning which of the near-duplicate methods is the one you meant.

Frequently asked questions

How do I install the countries gem?

Run gem install countries, or add bundle add countries to a Rails Gemfile. The gem covers ISO 3166-1 for countries, ISO 3166-2 for states and subdivisions, ISO 4217 for currency and E.164 for telephone numbers. The same data is also published as git submodules in the countries-data-yaml and countries-data-json repositories.

What does the states method return?

It is a deprecated alias of subdivisions and returns all subdivisions regardless of type, so it includes outlying areas and districts as well as states. For a filtered result use subdivisions_of_types, which takes an array of type strings such as ['state'], and pair it with subdivision_types or humanized_subdivision_types to see what a given country defines.

What type are the latitude and longitude values?

Strings. The README examples show c.latitude returning "37.09024" as a quoted value. The latitude_dec and longitude_dec accessors that would have given numbers were deprecated in 4.2 and removed in 5.0, so numeric consumers need to convert the value themselves.

How do I look up a country by name?

Use the attribute-based finders, which are case insensitive and ignore accents: find_country_by_iso_short_name('italy'), find_country_by_any_name('united states'), or find_all_by(:translated_names, 'França'). The older find_by_name and find_*_by_names methods were removed in 5.0. The list of searchable attributes is ISO3166::DEFAULT_COUNTRY_HASH.

What is the difference between iso_short_name and common_name?

iso_short_name is the official ISO 3166 short name, while common_name is a shortcut for the English translation, so translations('en'). That distinction is the reason the gem has separate name attributes, introduced in 4.2.0 to resolve confusion between official ISO names and the common names people actually type.

Does the countries gem provide timezone data?

Optionally. You have to add tzinfo to your Gemfile yourself with a requirement of ~> 1.2 and >= 1.2.2 and ensure it is required, because the gem will not do it for you. With tzinfo present you get c.timezones.zone_identifiers returning entries like America/New_York, plus zone_info for per-country data.

Official sources

  1. countries/countries on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/countries-countries.svg)](https://hysenlabs.com/projects/countries-countries)