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到参数中也无效。
原因分析
- 实例加载逻辑错误:
user方法中使用User.build(params[:user])会创建新的User实例,而非加载数据库中已存在的目标用户记录,属于update动作的逻辑错误。 - update参数传递错误:
user.update(user)将新建的实例对象作为参数传入,该实例的updated_by_id因原请求参数未包含而默认为nil,执行update时会直接覆盖数据库中用户的updated_by_id为nil。 - 关联更新不触发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
相关产品推荐
相关产品推荐

