You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Ruby on Rails:如何在预订表单中添加服务选择下拉功能?

嘿,我刚好在Rails项目里用bookable gem实现过类似的服务选择功能,给你整理了一套一步步的实现方案,应该能直接套用:

1. 先搞定模型关联

首先得让你的Booking和Service模型建立正确的关联——毕竟预订和服务是绑定的。如果一个预订可以选多个服务,推荐用多对多关联(更便于后续扩展):

# app/models/booking.rb
class Booking < ApplicationRecord
  # 用through关联比HABTM更灵活,后续可给中间表加额外字段(比如服务单价)
  has_many :booking_services, dependent: :destroy
  has_many :services, through: :booking_services
end

# app/models/service.rb
class Service < ApplicationRecord
  has_many :booking_services, dependent: :destroy
  has_many :bookings, through: :booking_services
end

然后生成中间表的迁移文件:

rails generate migration CreateBookingServices service:references booking:references

运行迁移生效:

rails db:migrate
2. 重写预订表单视图

bookable gem自带的表单没有服务选择项,所以先把gem的视图文件复制到本地项目,方便自定义:

rails generate bookable:views

之后你就能在app/views/bookable/bookings/找到表单模板(比如_form.html.erb),在合适的位置添加服务选择的下拉框,以及已选服务的展示区:

<!-- 在预订表单中添加服务选择 -->
<div class="form-group mb-3">
  <%= form.label :service_ids, '选择需要的服务', class: 'form-label' %>
  <!-- multiple: true 开启多选,不需要的话可以删掉 -->
  <%= form.collection_select :service_ids, Service.all, :id, :name, 
      { include_blank: '请选择服务' }, 
      { multiple: true, class: 'form-select', size: 5 } %>
</div>

<!-- 已选服务展示区 -->
<div class="selected-services mb-3">
  <h5 class="mb-2">已选择的服务:</h5>
  <% if @booking.services.any? %>
    <ul class="list-group">
      <% @booking.services.each do |service| %>
        <li class="list-group-item d-flex justify-content-between align-items-center">
          <%= service.name %>
          <button type="button" class="btn btn-sm btn-danger remove-service" data-service-id="<%= service.id %>">
            移除
          </button>
        </li>
      <% end %>
    </ul>
  <% else %>
    <p class="text-muted">暂未选择任何服务</p>
  <% end %>
</div>
3. 允许表单参数

bookable的默认控制器不会处理service_ids参数,所以我们要自定义控制器继承它,重写参数白名单:

# app/controllers/bookings_controller.rb
class BookingsController < Bookable::BookingsController
  private

  def booking_params
    # 把你原本的预订参数(比如start_time、end_time、customer_id)和service_ids数组加进去
    params.require(:booking).permit(
      :start_time, :end_time, :customer_id,
      service_ids: [] # 数组形式允许多个服务ID
    )
  end
end

然后修改路由,让bookable的请求指向我们的自定义控制器:

# config/routes.rb
# 先保留bookable的基础挂载
mount Bookable::Engine => "/bookable", as: :bookable
# 覆盖预订相关路由
resources :bookings, controller: 'bookings'
4. 前端实时交互(可选)

如果想要用户选择服务后立刻看到已选列表,不用刷新页面,可以加一段简单的JS:

// app/assets/javascripts/bookable.js
document.addEventListener('DOMContentLoaded', function() {
  const serviceSelect = document.querySelector('#booking_service_ids');
  const selectedList = document.querySelector('.selected-services ul');

  // 更新已选服务列表的函数
  function updateSelectedServices() {
    selectedList.innerHTML = '';
    // 遍历选中的选项,生成列表项
    Array.from(serviceSelect.selectedOptions).forEach(opt => {
      const li = document.createElement('li');
      li.className = 'list-group-item d-flex justify-content-between align-items-center';
      li.textContent = opt.textContent;
      
      const removeBtn = document.createElement('button');
      removeBtn.className = 'btn btn-sm btn-danger remove-service';
      removeBtn.dataset.serviceId = opt.value;
      removeBtn.textContent = '移除';
      // 移除按钮逻辑
      removeBtn.addEventListener('click', () => {
        const targetOpt = serviceSelect.querySelector(`option[value="${opt.value}"]`);
        targetOpt.selected = false;
        updateSelectedServices();
      });
      
      li.appendChild(removeBtn);
      selectedList.appendChild(li);
    });
    // 如果没有选中项,显示提示
    if (serviceSelect.selectedOptions.length === 0) {
      selectedList.innerHTML = '<p class="text-muted">暂未选择任何服务</p>';
    }
  }

  // 初始加载时更新列表(编辑场景用)
  updateSelectedServices();
  // 选择变化时更新列表
  serviceSelect.addEventListener('change', updateSelectedServices);
});
5. 可选:添加验证

如果要求用户必须选择至少一个服务,可以在Booking模型加验证:

# app/models/booking.rb
validates :services, presence: { message: '请至少选择一项服务' }

做完这些步骤后,你点击客户旁的「Book」按钮,弹出的表单里就会有服务选择下拉,选完后下方会实时展示已添加的服务,提交后预订也会正确关联服务啦~

内容的提问来源于stack exchange,提问作者Razz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 07:48:57