Rails 3 Select Tag with List of Countries

This post is to assist to create a select tag in a view (HTML) with a list of countries.

The solution presented here:

– The countries are stored in a table in database. If you don’t want to have a table with countries in DB, you can easily modify the code below to have countries as an array in code.

– Helper to show a list of countries in select tag in HTML

 

You can find the whole solution on github.

Data – list of countries

You have several options to get countries for your Rails application:

– create a table in your database and import data in raw SQL format.

You can find raw SQL file with countries and states here – http://27.org/isocountrylist/.

– create a migration to populate database with data

– have a list of countries in your code without creating a table in database.

 

Rails migration for a list of countries and states

– download this file with seed data

save this file to /db/seed/state_country_seeds.rb

 

if you want to generate your own data with countries you can start from this script:

https://github.com/wdblair/Rails-Seed-All-Countries-and-All-States

It has data in a txt-file as well as a PHP script to create a ruby code with seed data.

 

– generate models for countries and states:

 

rails g model Country
 
rails g model State

 

 

– create a new migration file to create table structures:

rails g migration CreateCountries

 

the tables will be as follows:

countries:
	id
	name
	iso #An ISO abbreviation for the country
states:
	id
	name
	country_id #the parent country's id, references an entry in countries.
	iso #An ISO abbreviation for the region.

 

– edit generated migration file  (in /db/migrate/XXX_load_countries.rb)

class Countries < ActiveRecord::Migration
  def up
    create_table :countries do |t|
    t.column :iso, :string, :size => 2
    t.column :name, :string, :size => 80
 
    end
 
    create_table :states do |t|
      t.column :name, :string, :size => 80
      t.column :country_id, :integer
      t.column :iso, :string, :size=>2
 
    end
 
  end
 
  def down
    drop_table :countries
    drop_table :states
  end
end

– create a new migration for loading data

rails g migration LoadCountries

– put this code in the file:

class LoadCountries < ActiveRecord::Migration
  def up
 
    # load
    require File.expand_path('../../seed/state_country_seeds.rb', __FILE__)
 
  end
 
  def down
    Country.delete_all
    State.delete_all
  end
end

- now we are ready to apply migrations.
rake db:migrate

 

No table solution:

if you don’t want to create a table for countries then you can have them in code.

 

Create a new class Country and put this code from this file.

so it will look like this:

class Country
  COUNTRIES_LIST = [{:iso => 'AF', :name => 'AFGHANISTAN', :printable_name => 'Afghanistan', :iso3 => 'AFG', :numcode => '004'},
    {:iso => 'AL', :name => 'ALBANIA', :printable_name => 'Albania', :iso3 => 'ALB', :numcode => '008'},
    {:iso => 'DZ', :name => 'ALGERIA', :printable_name => 'Algeria', :iso3 => 'DZA', :numcode => '012'},
...
    {:iso => 'ZW', :name => 'ZIMBABWE', :printable_name => 'Zimbabwe', :iso3 => 'ZWE', :numcode => '716'}]
 
  def self.find_by(property, value)
    COUNTRIES_LIST.each do |country|
      return country if country[property.to_sym] == value
    end
  end
end

 

HTML – Helper to get HTML code with select list of countries

 

– add a column to store country to your model

 

generate a new migration:

rails g migration AddCountryToUser

 

migration file:

class AddCountryToUser < ActiveRecord::Migration
  def change
    add_column :users, :country_id, :integer
  end
end

run migration

rake db:migrate

 

– create a new helper file – /app/helpers/user_form_helper.rb with this code:

module UserFormHelper
 
  # attributes - general
 
  def make_attributes(hsh)
    unless hsh.nil?
      output = ""
      hsh.each {|key, val| output << "#{key}=\"#{val}\""}
    end
    output
  end
 
  def country_code_select(object, method, priority_countries=nil, options={}, html_options={})
 
    countries = [Country.all.collect{|r| r.name}, Country.all.collect{|r| r.id}].transpose.sort
 
  if priority_countries.nil?
    output = select(object, method, countries, options, html_options)
  else
    output = "<select id='#{object}_#{method}' name='#{object}[#{method}]' #{make_attributes(html_options)}>\n"
    output << "<option value=\"0\">-- Select --</option>" if options[:include_blank] == true
 
    # get ids of priority_countries if needed
    unless priority_countries[0].respond_to?(:id)
      # get ids by an array of iso codes
      priority_countries_ids = Country.where(:iso=>priority_countries).all
    else
      priority_countries_ids = priority_countries
    end
 
    #
    priority_countries_ids.each do |pc|
      if options[:selected] == pc[:id]
        output << "<option value=\"#{pc[:id]}\" selected=\"selected\">#{pc[:name]}</option>"
      else
        output << "<option value=\"#{pc[:id]}\">#{pc[:name]}</option>"
      end
    end
 
    output << "<option value=\"0\" disabled=\"disabled\">----------------------------</option>"
 
    output << options_for_select(countries, options[:selected])
    output << "</select>\n"
  end
 
  output.html_safe
 
  end
end

- use helper in a view (HAML)
= country_code_select(:user, :country_id,
                  priority_countries=['US','GB'],
                  {:selected=>@user.country_id, :include_blank=>false},
                  {:style=>''})