name_bank
Authentic, gender-matched given and family names for 106 countries. Deep pools (up to 1500 per country/gender), uniform sampling, deterministic, no runtime dependencies.
Realistic fake name data for database seeds, factories, fixtures and demo environments — a companion to Faker and FFaker for the name part.
Installation
Add to your Gemfile:
gem "name_bank"
Then run bundle install. Or install directly:
gem install name_bank
Usage
require "name_bank"
rng = Random.new(1234)
# A full name (given + family) for a country and gender:
NameBank.full_name(country: "DE", gender: :female, rng: rng)
# => { firstname: "Sabine", lastname: "Müller" }
# Just a given name or a surname:
NameBank.first_name(country: "IT", gender: :male, rng: rng) # => "Amor"
NameBank.last_name(country: "JP", rng: rng) # => "Tsuru"
# Latin (default), or the country's native script where it has one:
NameBank.first_name(country: "JP", gender: :female, rng: rng) # => "Sasahara"
NameBank.first_name(country: "JP", gender: :female, rng: rng, script: :native) # => "ちゃこ"
# Default pool, or an alternate cultural pool where one exists:
NameBank.first_name(country: "US", gender: :male, rng: rng) # => "Abe"
NameBank.first_name(country: "US", gender: :male, rng: rng, variant: "african_american") # => "Roosevelt"
# List available countries (ISO alpha-2 codes):
NameBank.countries.size # => 106
NameBank.countries.first(3) # => ["AE", "AF", "AL"]
Sampling is uniform over each pool and fully deterministic for a given
rng — the same seed always yields the same name. gender: is :male
or :female.
Whole pools
Where sampling one name is not enough — drawing many names without repeats,
applying your own weighting, or checking what a country actually ships —
take the pool itself. first_names and last_names accept the same
country:, variant: and script: options as the samplers, and return the
frequency-ordered pool as a frozen array:
NameBank.first_names(country: "DE", gender: :female).size # => 1500
NameBank.first_names(country: "DE", gender: :female).first(3) # => ["Nicole", "Sandra", "Sabine"]
NameBank.last_names(country: "JP", script: :native).first(3) # => ["佐藤", "鈴木", "田中"]
Every method shown so far is also available on an instance, which lets you
point name_bank at your own directory of pool files:
NameBank.new(data_dir: "…").first_name(country: "DE", gender: :male, rng: rng).
Where it fits
A factory_bot factory:
FactoryBot.define do
factory :user do
transient do
country { "DE" }
person_gender { :female }
rng { Random.new }
person { NameBank.full_name(country: country, gender: person_gender, rng: rng) }
end
first_name { person[:firstname] }
last_name { person[:lastname] }
end
end
Database seeds with a fixed seed, so every run produces the same data:
# db/seeds.rb
rng = Random.new(20_260_724)
%w[DE FR IT ES PL].each do |country|
100.times do
person = NameBank.full_name(country: country, gender: [:male, :female].sample(random: rng), rng: rng)
User.create!(first_name: person[:firstname], last_name: person[:lastname], country: country)
end
end
An RSpec example — the seeded RNG makes the case reproducible:
it "fits a long Cyrillic name into the invoice header" do
rng = Random.new(1234)
customer = NameBank.full_name(country: "RU", gender: :female, rng: rng, script: :native)
header = InvoicePdf.new(customer).header
expect(header).to include(customer[:lastname])
end
Relation to Faker and FFaker
Faker and FFaker are full fake-data suites — addresses, companies, lorem ipsum, and much more. name_bank does one thing: people names. Use it alongside them, not instead of them.
| Faker | FFaker | name_bank | |
|---|---|---|---|
| Scope | full fake-data suite | full fake-data suite | people names only |
| Names addressed by | 58 language/region locales (de, de-AT, en-US) |
30 language modules (FFaker::NameDE) |
106 ISO country codes (DE, RU, JP) |
| Gendered given names | in about 24 of those locales | in 17 of the 30 name modules | in every country |
| Pool depth | uneven: en 1219 m / 4271 f, de 574 / 585, ru 52 / 56, ko 21 |
varies per module | up to 1500 per country, gender and script; 89 of 106 at the cap |
| Native script | one form per locale | one form per module | Latin and native as separate pools, 35 countries |
| Randomness | global Faker::Config.random |
global FFaker::Random.seed |
rng: passed in per call, no global state |
Counts measured against faker and ffaker main on 2026-07-24.
Moving a name call over:
Faker::Name.first_name
FFaker::NameDE.first_name
# both: gender-agnostic, locale/module picked from global state
NameBank.first_name(country: "DE", gender: :female, rng: rng)
# country and gender explicit, RNG explicit
Scripts
Names come in Latin (default) and, for countries with a non-Latin writing
system, their native script. Pass script::
NameBank.first_name(country: "RU", gender: :male, rng: rng) # => "Dmitry"
NameBank.first_name(country: "RU", gender: :male, rng: rng, script: :native) # => "Алексей"
NameBank.scripts(country: "RU") # => [:latin, :native]
NameBank.scripts(country: "DE") # => [:latin]
Germany's script is Latin, so DE reports :latin only.
:latin and :native sample from independent pools. For Latin-script countries
:native returns the same (Latin) pool. Requesting a script with no names
raises NameBank::EmptyPool; a script: that is neither :latin nor
:native raises NameBank::UnknownScript.
scripts takes an optional variant:, because a variant can offer different
forms from the country it is layered on:
NameBank.scripts(country: "US", variant: "african_american") # => [:latin]
Variants
Some countries offer an alternate cultural name pool layered on the default.
Pass variant::
NameBank.first_name(country: "US", gender: :male, rng: rng, variant: "african_american")
# => "DeShawn"
NameBank.variants(country: "US") # => ["african_american"]
NameBank.variants(country: "DE") # => []
Errors
Every error name_bank raises carries NameBank::Error, so one rescue covers
the lot. Each also keeps its natural Ruby superclass, so rescue ArgumentError
still catches the two that are genuinely caller mistakes:
| Error | Superclass | Raised when |
|---|---|---|
NameBank::UnknownGender |
ArgumentError |
gender: is neither :male nor :female |
NameBank::UnknownScript |
ArgumentError |
script: is neither :latin nor :native |
NameBank::UnknownCountry |
StandardError |
no pool file for that country code |
NameBank::UnknownVariant |
StandardError |
no pool file for that variant |
NameBank::EmptyPool |
StandardError |
the pool exists but holds no names |
NameBank::MalformedPool |
StandardError |
a pool file is missing a key, or holds something other than a list |
The last two cannot occur with the shipped data; they matter when you point
NameBank.new(data_dir:) at pool files of your own.
begin
NameBank.first_name(country: "ZZ", gender: :male, rng: rng)
rescue NameBank::UnknownCountry => e
warn "no pool for #{e.}"
end
Pool file format
NameBank.new(data_dir:) reads pools from a directory of your own. It holds
two subdirectories:
my_names/
countries/
DE.yml
RU.yml
variants/
US/
african_american.yml
countries/<code>.yml— the basename is what you pass ascountry:, and whatcountrieslists.variants/<code>/<name>.yml— the basename is what you pass asvariant:. No directory for a country meansvariants(country:)returns[].- Files that do not end in
.ymlare ignored, and codes are not validated against ISO 3166 — any basename works.
Names resolve the same way on every filesystem: an exact match wins, otherwise
a unique match ignoring case, so country: "de" finds DE.yml on Linux as
well as macOS. Two files whose basenames differ only in case are ambiguous, and
raise NameBank::UnknownCountry rather than being guessed at.
A pool file must carry the three keys in NameBank::PoolSchema::KEYS, each a
list. Any of them may have a native-script counterpart under the same key plus
_native:
firstnames_male: [Dmitry, Ivan]
firstnames_female: [Anna, Olga]
lastnames: [Ivanov, Petrov]
firstnames_male_native: [Дмитрий, Иван]
lastnames_native: [Иванов, Петров]
Variant files are validated the same way and need all three keys too. They may
carry _native pools, which script: :native will use — the shipped variants
do not, because the build pipeline strips them.
Missing a required key, or holding something other than a list under it, raises
NameBank::MalformedPool when the file is read. A key that is present but
empty is accepted at read time and raises NameBank::EmptyPool when sampled.
Any other key is ignored; the shipped files carry source:, which nothing
reads at runtime.
Sampling is uniform over the whole list, so order does not weight anything. It
is preserved, and visible through first_names and last_names.
Pool schema
NameBank::PoolSchema names the YAML keys a pool file uses. It is supported
public API from 0.2.0 on: the constants and their values will not change
without a version bump. Use it when you build pool files for
NameBank.new(data_dir:), so you are not typing key names by hand.
NameBank::PoolSchema::KEYS
# => ["firstnames_male", "firstnames_female", "lastnames"]
NameBank::PoolSchema::GIVEN_MALE # => "firstnames_male"
NameBank::PoolSchema::GIVEN_FEMALE # => "firstnames_female"
NameBank::PoolSchema::SURNAMES # => "lastnames"
KEYS is frozen and lists the three pools every file must carry. A file may
also carry a native-script pool for any of them, under the same key with a
_native suffix:
NameBank::PoolSchema.native_key("lastnames") # => "lastnames_native"
gender_key maps a gender: argument to its key, and raises
NameBank::UnknownGender for anything else:
NameBank::PoolSchema.gender_key(:female) # => "firstnames_female"
NameBank::PoolSchema.gender_key(:x) # raises NameBank::UnknownGender
Reading a file that is missing one of KEYS, or that holds something other
than a list under it, raises NameBank::MalformedPool.
Supported countries
106 countries: Afghanistan, Albania, Algeria, Angola, Argentina, Austria, Azerbaijan, Bahrain, Bangladesh, Belgium, Bolivia, Botswana, Brazil, Brunei, Bulgaria, Burkina Faso, Burundi, Cambodia, Cameroon, Canada, Chile, China, Colombia, Costa Rica, Croatia, Cyprus, Czechia, Denmark, Djibouti, Ecuador, Egypt, El Salvador, Estonia, Ethiopia, Fiji, Finland, France, Georgia, Germany, Ghana, Greece, Guatemala, Haiti, Honduras, Hong Kong, Hungary, Iceland, India, Indonesia, Iran, Iraq, Ireland, Israel, Italy, Jamaica, Japan, Jordan, Kazakhstan, Kuwait, Lebanon, Libya, Lithuania, Luxembourg, Macau, Malaysia, Maldives, Malta, Mauritius, Mexico, Moldova, Morocco, Namibia, Netherlands, Nigeria, Norway, Oman, Palestine, Panama, Peru, Philippines, Poland, Portugal, Puerto Rico, Qatar, Russia, Saudi Arabia, Serbia, Singapore, Slovenia, South Africa, South Korea, Spain, Sudan, Sweden, Switzerland, Syria, Taiwan, Tunisia, Turkey, Turkmenistan, Ukraine, United Arab Emirates, United Kingdom, United States, Uruguay, Yemen.
Pool sizes
Each country provides up to 1500 given names per gender and up to 1500 surnames; 89 of the 106 countries reach that cap on all three Latin pools, and the rest are as large as the source data allows. 35 countries also carry a native-script pool. Full per-country counts (Latin and native): docs/name-counts.md.
License
Apache-2.0.