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

Rails惯用方式实现:用复选框批量选择并保存API返回的高尔夫球场数据

Rails惯用方式实现:用复选框批量选择并保存API返回的高尔夫球场数据

我太懂这种一个个点保存的痛苦了!咱们直接改成Rails惯用的批量提交方案,核心就是用单个父表单包裹所有可选球场,通过复选框标记要保存的项,再利用嵌套属性一次性提交多条数据。下面一步步来改:


第一步:给控制器加批量创建的动作

首先在Admin::GolfCoursesController里新增batch_create方法,用来处理批量提交的请求,同时做好重复校验和错误收集:

class Admin::GolfCoursesController < Admin::BaseController
  # 保留你原有的search、new、create方法...

  def batch_create
    # 提取用户选中的球场数据,没有选中的话就设为空数组
    selected_courses = params[:golf_courses]&.values || []
    
    saved_count = 0
    error_messages = []

    selected_courses.each do |course_attrs|
      # 后端再做一次重复校验,避免前端漏判或者用户手动篡改参数
      next if GolfCourse.exists?(remote_api_id: course_attrs[:remote_api_id])

      course = GolfCourse.new(course_attrs)
      if course.save
        saved_count += 1
      else
        error_messages << "球场「#{course_attrs[:name]}」保存失败:#{course.errors.full_messages.to_sentence}"
      end
    end

    # 组装提示信息,兼顾成功和失败的情况
    combined_notice = []
    combined_notice << "成功保存 #{saved_count} 个球场" if saved_count.positive?
    combined_notice += error_messages if error_messages.any?

    if combined_notice.any?
      redirect_to admin_golf_courses_path, notice: combined_notice.join(";")
    else
      redirect_to admin_golf_courses_path, alert: "没有选中任何未保存的球场"
    end
  end

  private

  # 保留你原有的course_params方法...

  # 新增批量参数的强校验规则,对应前端的参数结构
  def batch_course_params
    params.permit(
      golf_courses: [
        :remote_api_id, :name, :phone,
        holes_attributes: [
          :hole, :par,
          tees_attributes: [:name, :yardage, :par] # 这里根据你实际的字段调整
        ]
      ]
    )
  end
end

别忘了在routes.rb里加对应的批量提交路由:

namespace :admin do
  resources :golf_courses do
    collection do
      post :search
      post :batch_create # 新增这条批量创建的路由
    end
  end
end

第二步:重构视图,改成单表单+复选框结构

把原来的每个独立表单,改成一个大表单包裹所有可选球场,用复选框控制是否提交该球场的数据,已存在的球场直接标记为“已保存”:

<h1>Courses from API: <%= @courses.count %></h1>

<%= form_with url: admin_golf_courses_batch_create_path, local: true do |form| %>
  <div class="grid grid-cols-1 gap-6">
    <% @courses.each_with_index do |course_data, course_idx| %>
      <% existing_course = GolfCourse.find_by(remote_api_id: course_data[:remote_api_id]) %>
      
      <% if existing_course.present? %>
        <p class="mt-4 text-brand-green-vibrant font-semibold">已保存:<%= course_data[:name] %></p>
      <% else %>
        <div class="border p-4 rounded-lg hover:bg-gray-50 cursor-pointer">
          <!-- 复选框:控制是否提交该球场 -->
          <%= check_box_tag "golf_courses[#{course_idx}][selected]", "1", true, id: "course_#{course_idx}" %>
          <%= label_tag "course_#{course_idx}", course_data[:name], class: "ml-2 font-medium text-lg" %>

          <!-- 隐藏字段:存储球场基础数据 -->
          <%= hidden_field_tag "golf_courses[#{course_idx}][remote_api_id]", course_data[:remote_api_id] %>
          <%= hidden_field_tag "golf_courses[#{course_idx}][name]", course_data[:name] %>
          <%= hidden_field_tag "golf_courses[#{course_idx}][phone]", course_data[:phone] %>

          <!-- 嵌套球洞数据:用Rails的_attributes命名规则,自动关联创建 -->
          <% course_data[:holes].each_with_index do |hole, hole_idx| %>
            <%= hidden_field_tag "golf_courses[#{course_idx}][holes_attributes][#{hole_idx}][hole]", hole[:hole] %>
            <%= hidden_field_tag "golf_courses[#{course_idx}][holes_attributes][#{hole_idx}][par]", hole[:par] %>

            <!-- 嵌套Tee台数据 -->
            <% hole[:tees].each_with_index do |tee, tee_idx| %>
              <%= hidden_field_tag "golf_courses[#{course_idx}][holes_attributes][#{hole_idx}][tees_attributes][#{tee_idx}][name]", tee[:name] %>
              <%= hidden_field_tag "golf_courses[#{course_idx}][holes_attributes][#{hole_idx}][tees_attributes][#{tee_idx}][par]", tee[:par] %>
              <!-- 这里补充你需要的其他Tee台字段 -->
            <% end %>
          <% end %>
        </div>
      <% end %>
    <% end %>
  </div>

  <!-- 批量提交按钮 -->
  <div class="mt-8">
    <%= submit_tag "批量保存选中的球场", class: "bg-green-600 text-white py-2 px-6 rounded hover:bg-green-700" %>
  </div>
<% end %>

视图小优化(可选)

加个简单的JS,让用户点击球场卡片就能切换复选框状态,提升操作体验:

document.addEventListener('DOMContentLoaded', function() {
  document.querySelectorAll('.border.p-4').forEach(card => {
    card.addEventListener('click', function(e) {
      if (!e.target.tagName.match(/INPUT|LABEL/)) {
        const checkbox = this.querySelector('input[type="checkbox"]');
        checkbox.checked = !checkbox.checked;
      }
    });
  });
});

为什么这是Rails惯用的方式?

  1. 符合RESTful思想:用collection级别的batch_create动作,不破坏原有路由结构
  2. 嵌套属性原生支持:用holes_attributes和tees_attributes的命名规则,Rails会自动处理嵌套关联的创建,不用手动写关联逻辑
  3. 前后端双重校验:前端隐藏已存在的球场,后端创建时再做一次重复判断,彻底避免数据重复
  4. 清晰的参数结构:索引化的参数数组,Rails能自动解析成可直接传给new方法的属性哈希

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 10:09:29