Rails 6.1含accepts_nested_attributes_for时saved_changes为空求解决方案
Rails 6.1 解决
accepts_nested_attributes_for 导致 saved_changes 为空的方案 问题回顾
在Rails 6.1应用中,当模型使用accepts_nested_attributes_for关联子模型时,after_commit、before_save等回调里的saved_changes会返回空哈希,这是双重保存触发的已知问题,而Rails 7.1新增的专属配置无法在6.1版本中使用。以下是适配Rails 6.1的可行解决方案:
方案1:提前缓存变更记录
在before_save阶段手动缓存当前模型的变更,后续回调直接使用缓存值,绕过双重保存导致的saved_changes清空问题:
class Profile < ApplicationRecord belongs_to :user accepts_nested_attributes_for :user after_commit :run_worker, on: :update attr_reader :cached_changes before_save do @cached_changes = saved_changes.dup end private def run_worker return if @cached_changes.empty? # 此处执行后台任务逻辑,例如: # ProfileUpdateWorker.perform_async(id, @cached_changes) end end
方案2:改用previous_changes获取变更
after_commit回调中previous_changes会保留最近一次保存的完整变更记录,不受双重保存影响,可直接替换saved_changes使用:
class Profile < ApplicationRecord belongs_to :user accepts_nested_attributes_for :user after_commit :run_worker, on: :update private def run_worker changes = previous_changes return if changes.empty? # 执行后台任务逻辑,可根据业务过滤不需要的字段 end end
方案3:全局Monkey Patch修正回调逻辑
通过修改ActiveRecord的回调执行逻辑,模拟Rails 7.1的配置行为,全局解决该问题:
# 在config/initializers/active_record_fix.rb中添加 module ActiveRecord class Base def run_commit_callbacks(&block) if transaction_include_action?(:update) && !@_ran_commit_callbacks @_ran_commit_callbacks = true super(&block) elsif !transaction_include_action?(:update) super(&block) end end def reset_ran_commit_callbacks @_ran_commit_callbacks = nil end end end # 模型中添加重置标记的回调 class Profile < ApplicationRecord belongs_to :user accepts_nested_attributes_for :user after_commit :run_worker, on: :update after_commit :reset_ran_commit_callbacks private def run_worker return if saved_changes.empty? # 执行后台任务逻辑 end end
注意:Monkey Patch可能影响全局模型行为,需在测试环境充分验证后再部署。
方案4:拆分嵌套属性保存逻辑
在控制器中手动拆分主模型与嵌套模型的保存流程,避免触发双重保存:
class ProfilesController < ApplicationController def update @profile = Profile.find(params[:id]) user_attrs = params[:profile].delete(:user_attributes) ActiveRecord::Base.transaction do @profile.update!(params[:profile]) @profile.user.update!(user_attrs) if user_attrs.present? end # 手动触发后台任务,确保变更记录准确 ProfileUpdateWorker.perform_async(@profile.id, @profile.saved_changes) end end
内容的提问来源于stack exchange,提问作者gsumk
相关产品推荐
相关产品推荐

