# Terminal
rails new template --webpack
yarn add stimulus
rails g model state name abbr
rails g model county name state:belongs_to
rails g model city name county:belongs_to
rails g scaffold accounts name city:belongs_to
# app/javascript/packs/application.js
console.log('Hello World from Webpacker')
import { Application } from "stimulus"
import { definitionsFromContext } from "stimulus/webpack-helpers"
const application = Application.start()
const context = require.context("./controllers", true, /\.js$/)
application.load(definitionsFromContext(context))
# app/views/layouts/application.html.erb
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application' %>
# models/state.rb
class State < ApplicationRecord
has_many :counties
scope :name_asc, -> { order(name: :asc) }
scope :select_collection, -> { name_asc.map { |s| [s.name, s.id, { url: Rails.application.routes.url_helpers.state_counties_path(s) }] } }
end
# models/county.rb
class County < ApplicationRecord
belongs_to :state
has_many :cities
scope :name_asc, -> { order(name: :asc) }
scope :select_collection, -> { name_asc.map { |s| [s.name, s.id, { url: Rails.application.routes.url_helpers.state_county_cities_path(state_id: s.state_id, county_id: s.id) }] } }
end
# models/city.rb
class City < ApplicationRecord
belongs_to :county, optional: true
has_many :accounts
scope :name_asc, -> { order(name: :asc) }
scope :select_collection, -> { name_asc.map { |s| [s.name, s.id] } }
end
# accounts/_form.html.erb
<div data-controller='city-selector'>
<%= form.label :state_id %>
<%= form.select :state_id,
State.select_collection,
{ include_blank: true },
{
class: 'form-control',
data: {
target: 'city-selector.state_input',
action: 'city-selector#state_changed'
}
}
%>
<%= form.label :county_id %>
<%= form.select :county_id, [], {}, {
class: 'form-control',
data: {
target: 'city-selector.county_input',
action: 'city-selector#county_changed'
}
} %>
<%= form.label :city_id %>
<%= form.select :city_id, [], {}, { class: 'form-control',
data: {
target: 'city-selector.city_input',
city_id: account&.city_id,
county_id: account&.city&.county_id,
state_id: account&.city&.county&.state_id
} } %>
</div>
# config/routes.rb
Rails.application.routes.draw do
resources :states, only: [] do
# /states/:state_id/counties
resources :counties, only: :index do
resources :cities, only: :index
end
end
resources :accounts
root to: 'accounts#index'
end
# counties_controller.rb
class CountiesController < ApplicationController
def index
state = State.find(params[:state_id])
counties = state.counties
render json: counties.select_collection
end
end
# cities_controller.rb
class CitiesController < ApplicationController
def index
state = State.find(params[:state_id])
county = state.counties.find(params[:county_id])
cities = county.cities
render json: cities.select_collection
end
end
# app/javascript/packs/controllers/city_selector_controller.js
import { Controller } from 'stimulus'
export default class extends Controller {
initialize() {
console.log('hello from city-selector controller')
this.check_forms()
}
check_forms() {
if (this.city.getAttribute('data-county-id')){
this.state.value = this.city.getAttribute('data-state-id')
this.state_changed()
}
}
state_changed() {
var that = this
this.request_data(this.state_url, function (response) {
that.county.innerText = null;
that.city.innerText = null;
that.county.appendChild(document.createElement('option'))
for (let i = 0; i < response.length; i++) {
var opt = document.createElement('option')
opt.textContent = response[i][0]
opt.value = response[i][1]
opt.setAttribute('url', response[i][2]['url'])
that.county.appendChild(opt)
}
if (that.city.getAttribute('data-county-id')) {
that.county.value = that.city.getAttribute('data-county-id')
that.city.removeAttribute('data-county-id')
that.city.innerText = null;
that.county_changed()
}
})
}
county_changed() {
var that = this
this.request_data(this.county_url, function (response) {
that.city.innerText = null;
that.city.appendChild(document.createElement('option'))
for (let i = 0; i < response.length; i++) {
var opt = document.createElement('option')
opt.textContent = response[i][0]
opt.value = response[i][1]
that.city.appendChild(opt)
}
if (that.city.getAttribute('data-city-id')) {
that.city.value = that.city.getAttribute('data-city-id')
that.city.removeAttribute('data-city-id')
}
})
}
get state() {
return this.targets.find('state_input')
}
get county() {
return this.targets.find('county_input')
}
get city() {
return this.targets.find('city_input')
}
get state_url() {
return this.state.selectedOptions[0].getAttribute('url')
}
get county_url() {
return this.county.selectedOptions[0].getAttribute('url')
}
request_data(url, callback) {
Rails.ajax({
type: 'GET',
url: url,
success: callback,
error: function (response) { }
})
}
}