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

如何在Rails中通过Cocoon gem嵌套属性更新关联表多复选框值

多对多关联下批量创建Assignment记录的实现方案

场景概述

现有User、Client、Role三个模型,三者通过Assignment关联表建立多对多关系,每个Assignment记录对应一个用户、一个客户端、一个角色。当前使用Cocoon gem动态添加Assignment字段,需求是:提交用户表单时,若为同一个客户端选择多个角色,需在Assignment表中生成多条对应记录(每个角色对应一条,共享同一用户和客户端)。

现有模型代码

User 模型

class User < ApplicationRecord
  has_many :assignments, inverse_of: :user, dependent: :destroy
  has_many :roles, through: :assignments

  accepts_nested_attributes_for :assignments, reject_if: :all_blank, allow_destroy: true
end

Client 模型

class Client < ApplicationRecord
  has_many :assignments, inverse_of: :client, dependent: :destroy
  has_many :users, through: :assignments
end

Role 模型

class Role < ApplicationRecord
  has_many :assignments, inverse_of: :role, dependent: :destroy
  has_many :users, through: :assignments
end

Assignment 关联表模型

class Assignment < ApplicationRecord
  belongs_to :user
  belongs_to :role
  belongs_to :client
end

现有视图代码

form.html.erb

<%= f.fields_for :assignments, url: url do |assignment_form| %>
  <%= render partial: 'users/assignment_fields', locals: { f: assignment_form, user: @user } %>
<% end %>
<div>
  <%= link_to_add_association "添加", f, :assignments, partial: 'users/assignment_fields', render_options: { locals: { user: @user } } %>
</div>

_assignment_fields.html.erb

<%= f.collection_check_boxes :role_id, Role.all, :id, :short_name, html_options = {} %>

参数许可配置

[:first_name, :last_name, assignments_attributes: [:client_id, role_id: [] ] ]

提交参数示例

"user"=><ActionController::Parameters {"first_name"=>"a", "last_name"=>"d",  "assignments_attributes"=><ActionController::Parameters {"0"=><ActionController::Parameters {"client_id"=>"2", "role_id"=>["2", "4"], "id"=>"300"}

实现方案

1. 调整视图字段逻辑

修改_assignment_fields.html.erb,添加客户端选择框,让用户先指定客户端,再选择该客户端下的多个角色:

<div class="assignment-group">
  <%= f.select :client_id, Client.all.map { |c| [c.name, c.id] }, prompt: "选择客户端", class: "form-control mb-2" %>
  
  <div class="roles-checkboxes">
    <%= f.collection_check_boxes :role_id, Role.all, :id, :short_name do |b| %>
      <div class="form-check">
        <%= b.check_box class: "form-check-input" %>
        <%= b.label class: "form-check-label" %>
      </div>
    <% end %>
  </div>

  <%= link_to_remove_association "移除", f, class: "btn btn-sm btn-danger mt-2" %>
</div>

2. 控制器层处理参数拆分

在UsersController的参数许可方法中,将单个Assignment条目里的role_id数组拆分为多个独立的Assignment条目,每个条目对应一个角色和相同的客户端。同时处理编辑场景下的现有记录更新/删除:

def user_params
  params = params.require(:user).permit(
    :first_name, :last_name,
    assignments_attributes: [:id, :client_id, :role_id, :_destroy]
  )

  return params unless params[:assignments_attributes].present?

  new_assignments = []
  # 收集用户现有Assignment的id与对应角色ID映射
  existing_assignments = @user.assignments.each_with_object({}) do |a, hash|
    hash[a.id.to_s] = a.role_id.to_s
  end

  params[:assignments_attributes].each do |_, assignment|
    client_id = assignment[:client_id]
    # 整理选中的角色ID,过滤空值
    selected_role_ids = if assignment[:role_id].is_a?(Array)
                          assignment[:role_id].reject(&:blank?)
                        else
                          [assignment[:role_id]].reject(&:blank?)
                        end
    assignment_id = assignment[:id]

    if assignment_id.present?
      # 处理现有Assignment记录
      existing_role_id = existing_assignments[assignment_id]
      if selected_role_ids.include?(existing_role_id)
        # 保留当前记录,移除已存在的角色ID避免重复创建
        new_assignments << assignment.except(:role_id).merge(role_id: existing_role_id)
        selected_role_ids.delete(existing_role_id)
      else
        # 标记删除未选中的现有记录
        new_assignments << assignment.merge(_destroy: '1')
      end
    end

    # 为剩余选中的角色创建新的Assignment条目
    selected_role_ids.each do |role_id|
      new_assignments << { client_id: client_id, role_id: role_id }
    end
  end

  # 重新构建assignments_attributes的索引
  params[:assignments_attributes] = new_assignments.map.with_index { |a, i| [i, a] }.to_h
  params
end

3. 验证效果

提交表单后,原来包含role_id数组的参数会被拆分为多个独立的Assignment条目,例如:

"user"=><ActionController::Parameters {
  "first_name"=>"a", 
  "last_name"=>"d", 
  "assignments_attributes"=><ActionController::Parameters {
    "0"=><ActionController::Parameters {"client_id"=>"2", "role_id"=>"2", "id"=>"300"}, 
    "1"=><ActionController::Parameters {"client_id"=>"2", "role_id"=>"4"}
  }
}

Rails的嵌套属性机制会自动创建/更新对应的Assignment记录,实现一个客户端对应多个角色的需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 06:15:32