Ruby on Rails Admin表单实现国家、州、城市联动下拉菜单求助
1. 准备模型与关联
先创建三个模型并设置层级关联:
生成模型
运行终端命令生成模型文件:
rails generate model Country name:string rails generate model State name:string country:references rails generate model City name:string state:references
执行数据库迁移:
rails db:migrate
补充模型关联代码
修改 app/models/country.rb:
class Country < ApplicationRecord has_many :states, dependent: :destroy end
修改 app/models/state.rb:
class State < ApplicationRecord belongs_to :country has_many :cities, dependent: :destroy end
修改 app/models/city.rb:
class City < ApplicationRecord belongs_to :state end
2. 导入测试数据(可选)
在 db/seeds.rb 中添加测试数据方便调试:
# 创建国家 usa = Country.create(name: "美国") china = Country.create(name: "中国") # 创建对应州/省 california = usa.states.create(name: "加利福尼亚州") guangdong = china.states.create(name: "广东省") # 创建对应城市 california.cities.create(name: "洛杉矶") guangdong.cities.create(name: "广州")
运行命令导入数据:
rails db:seed
3. 配置后台控制器
假设你在后台管理Address资源,修改后台控制器添加AJAX处理动作:
添加控制器动作
编辑 app/controllers/admin/addresses_controller.rb:
class Admin::AddressesController < ApplicationController # 其他默认动作... # 根据国家ID获取对应州 def get_states @country = Country.find(params[:country_id]) @states = @country.states.order(:name) respond_to do |format| format.turbo_stream end end # 根据州ID获取对应城市 def get_cities @state = State.find(params[:state_id]) @cities = @state.cities.order(:name) respond_to do |format| format.turbo_stream end end end
更新路由配置
在 config/routes.rb 中添加后台路由:
namespace :admin do resources :addresses do collection do get :get_states get :get_cities end end end
4. 编写后台表单视图
在后台表单视图(如 app/views/admin/addresses/_form.html.erb)中添加三个下拉框:
<div class="form-group"> <%= label_tag :country_id, "国家" %> <%= select_tag :country_id, options_from_collection_for_select(Country.all.order(:name), :id, :name, @address.country_id), class: "form-control", data: { turbo_controller: "dropdown", turbo_target: "states" } %> </div> <div class="form-group"> <%= label_tag :state_id, "州/省" %> <div id="states"> <%= render "states_dropdown", state_id: @address.state_id %> </div> </div> <div class="form-group"> <%= label_tag :city_id, "城市" %> <div id="cities"> <%= render "cities_dropdown", city_id: @address.city_id %> </div> </div>
创建下拉框局部视图
创建 app/views/admin/addresses/_states_dropdown.html.erb:
<%= select_tag :state_id, options_from_collection_for_select(@states || [], :id, :name, state_id), class: "form-control", disabled: @states.nil? || @states.empty?, data: { turbo_controller: "dropdown", turbo_target: "cities" } %>
创建 app/views/admin/addresses/_cities_dropdown.html.erb:
<%= select_tag :city_id, options_from_collection_for_select(@cities || [], :id, :name, city_id), class: "form-control", disabled: @cities.nil? || @cities.empty? %>
创建Turbo Stream视图
创建 app/views/admin/addresses/get_states.turbo_stream.erb:
<%= turbo_stream.replace "states" do %> <%= render "states_dropdown", state_id: nil %> <% end %>
创建 app/views/admin/addresses/get_cities.turbo_stream.erb:
<%= turbo_stream.replace "cities" do %> <%= render "cities_dropdown", city_id: nil %> <% end %>
5. 编写联动逻辑的Turbo控制器
创建 app/javascript/controllers/dropdown_controller.js:
import { Controller } from "@hotwired/stimulus" export default class extends Controller { connect() { this.element.addEventListener("change", this.handleChange.bind(this)) } handleChange(event) { const selectedId = event.target.value const targetId = this.data.get("target") const targetElement = document.getElementById(targetId) if (!selectedId) { // 未选中值时清空目标下拉框 targetElement.innerHTML = `<select class="form-control" disabled></select>` return } // 构造请求URL let url if (this.element.id === "country_id") { url = `/admin/addresses/get_states?country_id=${selectedId}` } else if (this.element.id === "state_id") { url = `/admin/addresses/get_cities?state_id=${selectedId}` } // 发送Turbo请求更新下拉框 fetch(url, { headers: { "Accept": "text/vnd.turbo-stream.html" } }) } }
在 app/javascript/controllers/index.js 中注册控制器:
import DropdownController from "./dropdown_controller" application.register("dropdown", DropdownController)
6. 保存关联数据(可选)
如果你的Address模型需要关联这三个层级,先修改模型:
class Address < ApplicationRecord belongs_to :country, optional: true belongs_to :state, optional: true belongs_to :city, optional: true end
然后在控制器中允许参数:
def address_params params.require(:address).permit(:country_id, :state_id, :city_id, ...) # 其他业务参数 end
内容的提问来源于stack exchange,提问作者Rubydev

