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

在Rails中如何通过单个嵌套表单创建双向Friendship关联?

解决双向Friendship联结记录的无限循环问题

方案一:改进模型回调(推荐)

首先给Friendship模型添加唯一索引,防止重复记录:

  1. 生成迁移文件:
rails generate migration AddUniqueIndexToFriendships user_id:integer friend_id:integer
  1. 修改迁移文件,添加唯一约束:
class AddUniqueIndexToFriendships < ActiveRecord::Migration[7.0]
  def change
    add_index :friendships, [:user_id, :friend_id], unique: true
  end
end
  1. 执行迁移:
rails db:migrate

然后修改Friendship模型,通过命名回调+跳过回调+存在性检查解决循环:

class Friendship < ApplicationRecord
  belongs_to :user
  belongs_to :friend, class_name: 'User'

  after_create :create_reciprocal_friendship, unless: :reciprocal_exists?

  private

  def create_reciprocal_friendship
    # 创建反向关系时跳过当前回调,避免无限循环
    Friendship.create!(user_id: friend_id, friend_id: user_id) do |friendship|
      friendship.skip_callback(:create, :after, :create_reciprocal_friendship)
    end
  end

  def reciprocal_exists?
    # 检查反向关系是否已存在,避免重复创建
    Friendship.exists?(user_id: friend_id, friend_id: user_id)
  end
end

方案二:直接用SQL插入绕过回调

如果不想调整回调逻辑,可以直接用参数化SQL插入反向记录,完全绕过ActiveRecord回调:

class Friendship < ApplicationRecord
  belongs_to :user
  belongs_to :friend, class_name: 'User'

  after_create :create_reciprocal_friendship, unless: :reciprocal_exists?

  private

  def create_reciprocal_friendship
    # 参数化SQL避免注入风险,直接插入记录不触发回调
    ActiveRecord::Base.connection.execute(
      "INSERT INTO friendships (user_id, friend_id, created_at, updated_at)
       VALUES (?, ?, ?, ?)",
      friend_id, user_id, Time.now.utc, Time.now.utc
    )
  end

  def reciprocal_exists?
    Friendship.exists?(user_id: friend_id, friend_id: user_id)
  end
end

方案三:在控制器层面处理

如果不想在模型中添加回调,可将双向关系的创建逻辑移到UsersController的update方法中:

class UsersController < ApplicationController
  def update
    @user = User.find(params[:id])
    # 过滤空值并转为整数格式
    new_friend_ids = params[:user][:friend_ids].reject(&:blank?).map(&:to_i)
    current_friend_ids = @user.friend_ids

    # 处理新增好友:创建双向关系
    (new_friend_ids - current_friend_ids).each do |friend_id|
      Friendship.find_or_create_by(user_id: @user.id, friend_id: friend_id)
      Friendship.find_or_create_by(user_id: friend_id, friend_id: @user.id)
    end

    # 处理取消的好友:删除双向关系
    (current_friend_ids - new_friend_ids).each do |friend_id|
      Friendship.where(user_id: @user.id, friend_id: friend_id).destroy_all
      Friendship.where(user_id: friend_id, friend_id: @user.id).destroy_all
    end

    if @user.update(user_params.except(:friend_ids))
      redirect_to @user, notice: '用户信息更新成功'
    else
      render :edit
    end
  end

  private

  def user_params
    params.require(:user).permit(:full_name, friend_ids: [])
  end
end

注意:方案三需要手动处理新增和删除的双向关系,逻辑相对繁琐,但完全避免了模型回调的循环问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 07:25:04