Resources

Download Source Code

Summary

# state.rb
class State < ApplicationRecord
  belongs_to :country

  def self.select_values(country)
    return [] unless country
    country.states.pluck(:name, :id)
  end
end

# country.rb
class Country < ApplicationRecord
  has_many :states
  def self.select_values
    Country.all.map { |country| [country.name, country.id, { data: { url: data_url(country) }}]}
  end

  private

  def self.data_url(country)
    Rails.application.routes.url_helpers.country_states_path(country, format: :json)
  end
end

# routes.rb
Rails.application.routes.draw do
  root to: 'users#index'
  resources :users
  resources :country, only: [] do
    resources :states, only: :index
  end
end

# _form.html.erb
<%= form_with(model: user, local: true) do |form| %>
  
  ...

  <div class="field">
    <%= form.label :country_id %>
    <%= form.select :country_id, 
        options_for_select(Country.select_values, user&.country&.id), 
        { include_blank: true }, 
        { class: 'remote-select', data: { target: '#user_state_id' }} %>
  </div>

  <div class="field">
    <%= form.label :state_id %>
    <%= form.select :state_id, 
        options_for_select(State.select_values(user.country), user&.state&.id), 
        {}, 
        {} %>
  </div>

  <div class="actions">
    <%= form.submit class: 'btn btn-primary'%>
  </div>
<% end %>

# states_controller.rb
class StatesController < ApplicationController
  def index
    country = Country.includes(:states).find(params[:country_id])
    render json: country.states.select(:name, :id).map { |state| { id: state.id, name: state.name }}
  end
end

# application.js
$(function () {
  $(document).on('change', '.remote-select', function (e) {
    var target = $(this).attr('data-target')
    var url = $(this).find(":selected").attr('data-url')
    if (url){
      $.ajax({
        url: url,
        method: 'GET',
        success: function (json) {
          var target_select = $(target)
          target_select.empty()
          json.map(function (item) {
            value = $('<option></option>').attr('value', item.id).text(item.name)
            target_select.append(value)
          })
          target_select.trigger("chosen:updated")
        },
        error: function () { }
      })
    }
  })
})