Rails中after_save回调触发stack level too deep无限循环问题求助
解决QueueItem批准后队列重排的无限循环问题
问题背景
项目中Chapter与QueueItem为一对多关联,一个Chapter可对应多个QueueItem(用于多次修订),直到某条QueueItem被标记为approved(布尔值)。每个QueueItem包含position字段(整数,标识队列中的位置)。需求是当QueueItem被批准时重新排序队列,但当前将reorder_queue设为after_save回调后,因方法内部更新QueueItem会触发无限循环,导致stack level too deep错误。
现有代码
queue_item.rb 中的 reorder_queue 方法
def reorder_queue Chapter.where(should_be_queued: true).order("created_at ASC").each_with_index do | chapter, index | chapter.most_recent_queue_item.update(position: index + 1) end end
chapter.rb 中的 most_recent_queue_item 方法
def most_recent_queue_item item = QueueItem.where(chapter_id: self.id).last item end
解决方案
方案1:用update_columns替代update
update方法会触发模型的save回调,而update_columns直接修改数据库字段,不触发任何回调,从根源避免循环。修改reorder_queue方法:
def reorder_queue Chapter.where(should_be_queued: true).order("created_at ASC").each_with_index do | chapter, index | chapter.most_recent_queue_item.update_columns(position: index + 1) end end
注意:update_columns会跳过模型验证,需确保position字段无需额外验证逻辑。
方案2:限制回调触发条件
仅当approved字段发生变化且变为true时,才执行重排逻辑,避免不必要的回调触发,同时结合update_columns更新position:
- 修改queue_item.rb的回调定义:
after_save :reorder_queue, if: -> { saved_change_to_approved? && approved }
- 保持
reorder_queue中使用update_columns更新position。
方案3:临时跳过回调
若需要保留验证逻辑,可在更新position时临时跳过after_save回调:
def reorder_queue Chapter.where(should_be_queued: true).order("created_at ASC").each_with_index do | chapter, index | item = chapter.most_recent_queue_item item.skip_callback(:save, :after, :reorder_queue) item.update(position: index + 1) item.set_callback(:save, :after, :reorder_queue) end end
该方法先跳过回调,更新后再恢复,适合需要执行验证的场景,但代码相对复杂。
额外优化建议
most_recent_queue_item方法可简化为关联查询,利用Rails关联方法更高效:
def most_recent_queue_item queue_items.last end
因Chapter已定义has_many :queue_items关联,直接调用queue_items.last即可,无需手动编写where查询。
内容的提问来源于stack exchange,提问作者Liz
相关产品推荐
相关产品推荐

