Rails 7中如何处理指向同一模型的多个belongs_to关联
Rails 7 用户邀请系统双User关联建模方案
数据库层设计
首先给UserInvite表设置两个指向users表的外键:
inviter_id:必填,存储发起邀请的用户ID,创建邀请记录时就写入invitee_id:可选,存储接受邀请的新注册用户ID,邀请被接受时才写入
同时存储邀请令牌、发送时间、接受时间等业务字段,令牌需要加唯一索引。
迁移文件代码如下:
# db/migrate/[timestamp]_create_user_invites.rb class CreateUserInvites < ActiveRecord::Migration[7.0] def change create_table :user_invites do |t| t.references :inviter, null: false, foreign_key: { to_table: :users } t.references :invitee, null: true, foreign_key: { to_table: :users } t.string :token, null: false t.datetime :sent_at, null: false t.datetime :accepted_at t.timestamps end add_index :user_invites, :token, unique: true end end
执行rails db:migrate完成表创建。
模型关联配置
因为两个关联都指向User模型,不能用Rails默认的关联推导规则,需要手动指定关联对应的类名、外键,同时注意被邀请人关联是可选的,要关闭默认的必填校验。
UserInvite模型配置
# app/models/user_invite.rb class UserInvite < ApplicationRecord # 邀请人关联,创建时必填 belongs_to :inviter, class_name: "User" # 被邀请人关联,邀请接受前为空,设置为可选 belongs_to :invitee, class_name: "User", optional: true before_create :generate_unique_token, :set_sent_at validates :token, uniqueness: true private def generate_unique_token loop do self.token = SecureRandom.urlsafe_base64(16) break unless UserInvite.exists?(token: token) end end def set_sent_at self.sent_at = Time.current end end
User模型反向关联配置
需要区分用户作为邀请人发出的邀请、作为被邀请人收到的邀请两类关联:
# app/models/user.rb class User < ApplicationRecord # 当前用户发出的所有邀请 has_many :sent_invites, class_name: "UserInvite", foreign_key: :inviter_id, dependent: :destroy # 当前用户注册时绑定的邀请记录 has_one :received_invite, class_name: "UserInvite", foreign_key: :invitee_id, dependent: :nullify end
业务逻辑实现
- 发送邀请:已登录用户创建邀请记录时,仅需要绑定inviter为当前用户即可,不需要传入invitee_id,保存成功后将带token的邀请链接发送到被邀请人邮箱
# 邀请发送示例 invite = current_user.sent_invites.build invite.save! UserInviteMailer.with(invite: invite).send_invite.deliver_later
- 接受邀请:用户通过邀请链接提交注册表单,创建
User记录成功后,根据链接中的token找到对应邀请记录,更新invitee_id为新用户ID,同时记录接受时间
# 注册流程绑定邀请示例 def create @user = User.new(user_params) if @user.save invite = UserInvite.find_by!(token: params[:invite_token]) # 增加判断防止邀请被重复绑定 if invite.invitee_id.blank? invite.update!(invitee: @user, accepted_at: Time.current) end # 后续自动登录、跳转逻辑 else render :new, status: :unprocessable_entity end end
注意:Rails 7中
belongs_to默认开启存在性校验,invitee关联如果不加optional: true,创建邀请时会因为缺少invitee_id直接校验失败。
内容的提问来源于stack exchange,提问作者zcserei
相关产品推荐
相关产品推荐

