Dynamic Select Menus in Rails 5

Steps

Step 1

Generate the people controller and person, country and state models.

rails g controller people new
rails g model state name country_id:integer
rails g model country name 
rails g model person name country_id:integer state_id:integer

Step 2

The people controller new action initializes the person variable.

def new
  @person = Person.new
end

Step 3

The form to create a new person views/people/new.html.erb looks like this:

<h1>New Person</h1>
<%= form_for @person do |f| %>
  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :country_id %><br />
    <%= f.collection_select :country_id, Country.order(:name), :id, :name, include_blank: true %>
  </div>
  <div class="field">
    <%= f.label :state_id, "State or Province" %><br />
    <%= f.collection_select :state_id, State.order(:name), :id, :name, include_blank: true %>
  </div>
  <div class="actions"><%= f.submit %></div>
<% end %>

Step 4

Copy the countries.csv and states.csv to the root of your project. Populate the countries and states tables from csv files in seeds.rb

require 'csv'

puts "Importing countries..."
CSV.foreach(Rails.root.join("countries.csv"), headers: true) do |row|
  Country.create! do |country|
    country.id = row[0]
    country.name = row[1]
 . end
end

puts "Importing states..."
CSV.foreach(Rails.root.join("states.csv"), headers: true) do |row|
  State.create! do |state|
    state.name = row[0]
    state.country_id = row[2]
  end
end

Run the seed script.

rails db:seed

Step 5

Define the routes.

Rails.application.routes.draw do
  root to: 'people#new'

  resources :people
end

Step 6

Declare the associations for the models.

class State < ApplicationRecord
  belongs_to :country
  has_many :people
end
class Person < ApplicationRecord
  belongs_to :country
  belongs_to :state
end
class Country < ApplicationRecord
  has_many :states
  has_many :people
end

Step 7

Go to the home page, you will now see the country and state drop-down.

Step 8

Let's group the states by country.

<h1>New Person</h1>
<%= form_for @person do |f| %>
  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :country_id %><br />
    <%= f.collection_select :country_id, Country.order(:name), :id, :name, include_blank: true %>
  </div>
  <div class="field">
    <%= f.label :state_id, "State or Province" %><br />
    <%= f.grouped_collection_select :state_id, Country.order(:name), :states, :name, :id, :name, include_blank: true %>
  </div>
  <div class="actions"><%= f.submit %></div>
<% end %>

Step 9

Create people.js in app/assets/javascripts directory.

jQuery(function() {
  var states;
  states = $('#person_state_id').html();
  console.log(states);
  return $('#person_country_id').change(function() {
    var country, options;
    country = $('#person_country_id :selected').text();
    options = $(states).filter("optgroup[label=" + country + "]").html();
    console.log(options);
    if (options) {
      return $('#person_state_id').html(options);
    } else {
      return $('#person_state_id').empty();
    }
  });
});

Step 10

Delete the people.js.coffee. Reload the page. You will see the drop down boxes in the javascript console. Selecting a country will now populate the states only for that country in the state drop-down.

Step 11

Escape any special characters in the drop down values to avoid problems.

jQuery(function() {
  var states;
  $('#person_state_id').parent().hide();
  states = $('#person_state_id').html();
  console.log(states);
  return $('#person_country_id').change(function() {
    var country, escaped_country, options;
    country = $('#person_country_id :selected').text();
    escaped_country = country.replace(/([ #;&,.+*~\':"!^$[\]()=>|\/@])/g, '\\$1');
    options = $(states).filter("optgroup[label=" + escaped_country + "]").html();
    console.log(options);
    if (options) {
      $('#person_state_id').html(options);
      return $('#person_state_id').parent().show();
    } else {
      $('#person_state_id').empty();
      return $('#person_state_id').parent().hide();
    }
  });
});

Note: If you use:

<%= f.collection_select :country_id, Country.order(:name), :id, :name, include_blank: true  %>
<%= f.collection_select :state_id, State.order(:name), :states, :name, :id, :name, include_blank: true %>

You will get the error:

undefined method `merge' for :name:Symbol.

You can download the source code from https://github.com/bparanj/drops.

Summary

In this article, you learned how to populate drop-down values based on selection of another drop-down in Rails 5.


Related Articles

Watch this Article as Screencast

You can watch this as a screencast Dynamic Select Menus in Rails 5


Ace the Technical Interview

  • Easily find the gaps in your knowledge
  • Get customized lessons based on where you are
  • Take consistent action everyday
  • Builtin accountability to keep you on track
  • You will solve bigger problems over time
  • Get the job of your dreams

Take the 30 Day Coding Skills Challenge

Gain confidence to attend the interview

No spam ever. Unsubscribe anytime.