Rails 6.1 has_many :through关联配置及创建代码简化方法
Rails 6.1 正确配置
has_many :through关联及简化创建代码方案 现有代码的核心问题
- 桥接表
multicats设置了id: false缺失主键,且未配置外键约束、联合唯一索引,极易产生重复关联、孤儿数据等脏数据 - 模型关联的
dependent: :destroy配置位置错误,当前写在through关联上会在删除联系人/列表时,误删关联的列表/联系人本体,属于高危逻辑bug - 缺失桥接模型
Multicat的定义,未做基础数据校验 - 控制器手动创建桥接记录逻辑冗余,存在重复调用
save、无事务保护的问题,创建失败时容易产生数据不一致 - 强参数未遵循Rails关联命名约定,使用
:list接收参数才会触发「list属性不存在」的报错
第一步:修复数据库迁移
桥接表作为独立的Active Record模型必须要有主键,同时需要添加外键约束和联合唯一索引保证数据一致性,执行rails g migration FixMulticatsTable生成迁移,代码如下:
class FixMulticatsTable < ActiveRecord::Migration[6.1] def change # 删除原有无主键的异常表 drop_table :multicats create_table :multicats do |t| t.belongs_to :user, null: false, foreign_key: true t.belongs_to :list, null: false, foreign_key: true t.belongs_to :contact, null: false, foreign_key: true t.timestamps end # 新增联合唯一索引,避免同一联系人重复加入同一列表 add_index :multicats, [:list_id, :contact_id], unique: true end end
写完执行rails db:migrate完成表结构修复。
第二步:修正模型关联定义
首先补全桥接模型app/models/multicat.rb:
class Multicat < ApplicationRecord belongs_to :user belongs_to :list belongs_to :contact # 应用层校验重复关联 validates :contact_id, uniqueness: { scope: :list_id } end
修正Contact模型关联:
class Contact < ApplicationRecord belongs_to :user # 注意:dependent: :destroy 要加在桥接关联上,删除记录时只会清理桥接表数据,不会误删关联的列表 has_many :multicats, dependent: :destroy has_many :lists, through: :multicats # 自动填充桥接记录的user_id,无需控制器手动赋值 before_validation do multicats.each { |mc| mc.user ||= user } end end
修正List模型关联:
class List < ApplicationRecord belongs_to :user has_many :multicats, dependent: :destroy has_many :contacts, through: :multicats end
第三步:简化控制器创建逻辑
Rails原生支持通过list_ids=方法直接给has_many :through关联赋值,框架会自动完成桥接记录的创建、事务包裹,失败会整体回滚,不需要手动操作Multicat模型。
修正后的控制器代码如下:
def create # 通过current_user.contacts构建会自动填充contact的user_id,无需手动传参 @contact = current_user.contacts.new(contact_params) respond_to do |format| if @contact.save format.html { redirect_to contacts_url, notice: "联系人创建成功" } format.json { render :show, status: :created, location: @contact } else format.html { render :new, status: :unprocessable_entity } format.json { render json: @contact.errors, status: :unprocessable_entity } end end end private def contact_params # 用Rails约定的list_ids参数接收关联列表id,数组格式同时兼容单选、多选场景 params.require(:contact).permit(:first_name, :email, list_ids: []) end
表单适配说明
前端表单的列表选择字段不要使用list作为参数名,要改成contact[list_ids][],框架就能自动识别关联,不会再报属性不存在的错误。单选下拉示例:
<%= form.label :list_ids, "所属列表" %> <%= form.collection_select :list_ids, current_user.lists, :id, :name, { prompt: "请选择列表" }, multiple: false %>
如果需要支持多选,把multiple: false改为multiple: true即可,后端逻辑无需调整。
内容的提问来源于stack exchange,提问作者eugenedm
相关产品推荐
相关产品推荐

