Claude 对 Rails 回调的错误认知

我尝试运行一个 rake 任务来删除 LineItem 记录及其 S3 文件。我想避免在 Order 等父模型上触发昂贵的 callback。

我向 Claude 求助。它给出了一个充满自信的回答。但它是错的。

以下是我关于 Rails、counter_cache 以及为什么必须验证 AI 建议所学到的经验。

问题所在 LineItem 属于 OrderItemOrderItem 属于 Order。两者都使用了 counter_cachetouch。 删除一个 LineItem 会触发级联反应。这种级联会触发诸如运费估算和总额重新计算之类的重型任务。 我需要停止这种级联,以节省 CPU 和 S3 成本。

AI 的错误 Claude 建议使用 skip_callback。 这是一个坏主意。skip_callback 会全局修改类。它会影响应用中的每一个线程。如果你的代码在重新启用它之前崩溃了,你的 callback 将会一直处于失效状态。

然后我尝试了 no_touching。为了保险起见,我在 OrderItemOrder 的调用中都包裹了它。 测试通过了,但控制台显示的结果却不同。Order 的时间戳仍然发生了变化。

真实原因 问题在于 counter_cachetouch 的协作方式。

  • 当你同时使用 counter_cache: truetouch: true 时,Rails 会将它们捆绑在一起。
  • 它会运行一条单一的原始 SQL UPDATE ALL 命令。
  • 原始 SQL 会绕过 ActiveRecord 的生命周期。
  • 因为绕过了生命周期,after_commit 钩子不会触发。

这产生了一个奇怪的悖论:

  • 由于原始 SQL 的捆绑执行,OrderItem 的 callback 没有触发。
  • Order 的 callback 确实触发了,因为链条中的下一步是一个普通的 touch

解决方案 我只需要在“祖父级”模型上包裹 no_touching 即可。

Order.no_touching { line_item.destroy! }

这阻止了 AR 级别的 touch 传递到 Order 模型。它不会阻止 OrderItem 上的原始 SQL,但这并不重要,因为 counter_cache 的捆绑操作已经跳过了那些 callback。

核心启示

  • counter_cache: true + touch: true = 原始 SQL UPDATE ALL
  • 原始 SQL 会跳过所有的 after_commit 钩子。
  • 普通的 touch(不带 counter_cache)遵循标准的 AR 生命周期并触发 callback。
  • 永远不要盲目信任 AI 代码。Claude 只是想给你一个答案,它并不关心这个答案是否是幻觉。

务必在 Rails 控制台中测试你的假设。故意破坏代码,看看它是否真的停止了你想要阻止的行为。

Source: https://dev.to/husteadrobert/what-claude-thought-he-knew-about-rails-callbacks-and-how-console-testing-proved-him-wrong-j20

Optional learning community: https://t.me/GyaanSetuAi