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

Rails 5更新关联book_ids时updated_by字段被置空的问题求助

问题背景

在Rails 5项目中,存在User、Book和UserBook三个模型,其中user_books是用户与书籍的关联中间表。相关模型及控制器代码如下:

UserBook模型

class UserBook < ApplicationRecord
  belongs_to :user
  belongs_to :book
end

Book模型

class Book < ApplicationRecord
  has_many :user_books
end

User模型

class User < ApplicationRecord    
  has_many :user_books
  has_many :books, through: :user_books
  before_update :set_user

  private

  def set_user
    return unless User.current

    self.updated_by = User.current
  end
end

User控制器

class UserController < BasicCrudController
  def update
    if user.update(user)
      redirect_to user_path(user), notice: 'User was successfully updated.'
    else
      render :edit
    end
  end

  private

  def user
    @user ||= User.build(params[:user])
  end

  def user_params
    params.require(:user).permit(:name, book_ids: []).tap do |whitelisted|
      whitelisted[:updated_by_id] ||= current_user.id
    end
  end
end

添加book_ids参数后,更新该字段时updated_by会被置空。经排查,更新关联集合时User的before_update回调未触发,且尝试合并updated_by_id到参数中也无效。

原因分析

  1. 实例加载逻辑错误:user方法中使用User.build(params[:user])会创建新的User实例,而非加载数据库中已存在的目标用户记录,属于update动作的逻辑错误。
  2. update参数传递错误:user.update(user)将新建的实例对象作为参数传入,该实例的updated_by_id因原请求参数未包含而默认为nil,执行update时会直接覆盖数据库中用户的updated_by_id为nil。
  3. 关联更新不触发User回调:当仅更新book_ids时,Rails仅对中间表user_books执行增删操作,User模型的自身属性未发生变更,因此不会触发before_update回调,无法通过回调自动设置updated_by_id。

解决方案

1. 修复用户实例加载逻辑

将user方法改为加载数据库中已存在的用户:

private

def user
  @user ||= User.find(params[:id])
end

2. 修正update方法的参数传递

使用经过过滤的user_params作为update的参数,确保参数包含updated_by_id:

def update
  if user.update(user_params)
    redirect_to user_path(user), notice: 'User was successfully updated.'
  else
    render :edit
  end
end

3. 确保关联更新时也能设置updated_by

如果需要在仅更新书籍关联时也更新updated_by,可以通过以下两种方式实现:

  • 方式一:在参数中强制加入updated_at,触发User实例的更新逻辑:
def user_params
  params.require(:user).permit(:name, book_ids: []).tap do |whitelisted|
    whitelisted[:updated_by_id] = current_user.id
    whitelisted[:updated_at] = Time.current
  end
end
  • 方式二:在控制器中手动设置updated_by,无需依赖回调:
def update
  user.updated_by = current_user
  if user.update(user_params)
    redirect_to user_path(user), notice: 'User was successfully updated.'
  else
    render :edit
  end
end

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 05:33:20