【问题标题】:How to properly enforce a conditional read-only record on Rails?如何在 Rails 上正确执行条件只读记录?
【发布时间】:2025-12-25 07:50:06
【问题描述】:

所以工作中出现了一种情况,我想在这里讨论一下,因为我们之间无法达成协议:

我们有两个模型,OrderPassport,它们的关联方式是 Order has_one passport 和 passport has_many orders。每当订单完成时,其关联的护照必须被“锁定”,即变为只读(该信息已用于清关,因此以后无法更改)。我们希望在 Passport 模型中强制执行该规则,并且我们考虑了以下选项:

  1. 创建验证。缺点:当技术上记录很好时(尽管无法保存),会有记录产生valid? => false。例如,如果其他记录上有 validates_associated :passport,这可能是个问题。
  2. 覆盖readonly? 方法。缺点:尝试更新该记录时,这会引发异常,尽管您会认为调用 save 方法永远不会引发异常。
  3. 创建before_save 回调。这有两种风格:要么引发异常(这很像 readonly? 选项),要么添加 @error 并返回 false 以停止回调链。缺点:从正确的验证之外添加验证错误可能被认为是一种不好的做法。此外,您可能会发现自己调用valid? 并获得true,然后调用save 并获得false

这种情况让我们思考了很多关于验证和 Rails 之间的关系。记录为valid? 究竟意味着什么?这是否意味着save 会起作用?

我想听听您的意见以了解这种情况。也许最好的方法不是三者之一!谢谢!

【问题讨论】:

    标签: ruby-on-rails ruby validation activerecord


    【解决方案1】:

    如何使用readonly! 实例方法将此记录标记为只读?见API

    您可以在构造函数中执行此操作,例如:

    class Passport < ActiveRecord::Base
      def initialize(*args)
        super(*args)
        readonly! if orders.count>0 # or similar
      end
    end
    

    【讨论】:

    • 我认为如果在订单更新之前初始化护照对象会失败。
    • 这与覆盖readonly? 方法的行为不完全相同吗?我的意思是,尝试save 记录时会发生什么?例外?
    • 是的,一个例外ActiveRecord::ReadOnlyRecord
    【解决方案2】:

    我认为还有一个额外的选择。您所描述的表明Passport 模型可以有一些不同的状态。我会考虑使用状态机来描述护照的相关订单状态。

    例如:

    • 打开
    • 待定
    • 锁定
    • other_update_actions ...

    考虑到这一点,所有相关的订单操作都会触发护照模型及其状态的事件。

    如果可以将更新操作集成到某些事件,那么您可以以更优雅的方式处理只读部分(不兼容的状态转换)。

    作为一项额外检查,您始终可以保留一个 ugly 验证器作为最后的手段,以防止在没有状态机的情况下更新模型。

    你可以查看the aasm gem这个

    【讨论】: