【问题标题】:Is there a built-in approach in Ruby on Rails to avoid writing data in stale forms to the database?Ruby on Rails 中是否有一种内置方法可以避免将陈旧形式的数据写入数据库?
【发布时间】:2021-05-05 17:27:31
【问题描述】:

在我的应用中,多个用户可以编辑数据库中的同一个对象。如果 user1 将数据加载到表单中,而 user2 在 user1 加载表单但在 user1 提交数据之前将数据写入数据库,我想阻止保存 user1 的数据。

Rails 中是否有任何内置功能,或者 Ruby/Rails 社区库中是否有任何东西可以解决这个问题?

我可以自己尝试,通过在所有模型read_at 上具有所有 UI 元素发回的虚拟属性。但我知道问题可能比看起来更复杂,如果有内置的解决方案,我想知道。

除非预先构建解决方案,否则我是否可以考虑使用特定于 Rails 的特性?我知道有一个表单令牌?我想知道我是否可以从中获取时间信息?

【问题讨论】:

  • 您可以尝试在表单中设置一个隐藏字段last_updated_at,该字段在呈现表单时应该具有记录的updated_at 的值。如果其他人更新它,updated_at 会发生变化,您可以拒绝第一个用户的表单提交为params[:last_updated_at] < record.updated_at

标签: ruby-on-rails ruby staledataexception


【解决方案1】:

Andreas Gebhard 的回答中指向ActiveRecord::Locking::Optimistic 的链接从技术上说您需要知道的一切。但是,如果我为您拼写出来,可能会更清楚。

如果您在表中添加一个名为 lock_version 的字段,默认情况下 ActiveRecord 将启用乐观锁定。但是你还没有完成。

您遇到的问题称为“丢失更新问题”,即第二个人的更新破坏了之前个人的更新。如果您所做的只是在数据库层启用乐观锁定,如果您正常编程并且不采取必要的步骤,您的 Web 应用程序仍然会出现丢失更新的问题。

通常,您的更新操作会查找记录,然后将所有字段替换为从表单发送的值。就数据库和 ActiveRecord 所知,您从最近的一条新记录开始并对其进行了更新。因此乐观锁定将满足于允许更新,但您仍然有丢失更新的问题。

您需要做的是将lock_version 字段作为隐藏输入包含在您的表单中。那么当第二个进行更新的人尝试更新时,将 lock_version 设置为之前更新完成之前的旧值会导致乐观锁定失败。

在悲观锁中,避免丢失更新问题的关键是获取编辑表单必须先获取锁。如果其他人将其锁定,您将无法编辑。解决丢失更新问题的一个非常重要的关键是人在循环中通过在编辑之前呈现数据库中的值,这样只有人才能决定更改它们。在丢失的更新中,有人不知道其他人的更新,因此保存了之前的值而不知道它已被更新。

实际上,问题没有解决办法。总会有人输的。在丢失更新问题中,第一个保存的人会失败。在悲观(又名排他锁)中,第二个尝试获得锁的人会失败。在乐观锁定第二个人保存失败。

悲观/排他锁定的缺点是有人试图获取编辑表单被拒绝,因为其他人拥有锁定。此外,锁可能不会在应该释放的时候释放,您可能会遇到死锁。

乐观锁定的缺点是有人可以在编辑时完成所有工作,但在尝试保存时会被拒绝。

无论如何,对于试图编辑的人来说,这都是一个惊喜。丢失更新问题的缺点是最糟糕的情况是惊喜悄然发生,没有人注意到。至少通过排他或乐观锁定,用户会得到通知。使用乐观锁定,他们必须“合并”,可能不得不从新数据开始重做他们的编辑工作。使用独占锁定,他们永远不会遇到这个问题,但他们可能不得不等待并且可能不明白为什么。乐观锁定的普遍偏好是,大多数时候没有更新争用,它只是工作,而对于独占锁定,总是需要进入编辑模式并刷新编辑表单上的数据。

【讨论】:

  • 感谢您抽出宝贵时间详细说明此主题。我知道 SO 鼓励用户把事情说清楚,但这是非常基础的,有大量的 googleable 资源和官方文档,我真的认为没有必要这样做。不过,干得好!
  • @AndreasGebhard 如果您知道它是基本的,并且会询问 Rails 是否有解决“丢失更新问题”或内置锁定的方法。我感觉人们认为 activerecord 具有乐观锁定,所以也许你必须启用它,但你不必做任何事情,但你可能仍然会遇到更新丢失的问题,因为你必须确保表单正在通过lock_version 字段。所以文档是完整的,但可能会让人们一头雾水。
  • 我完全同意!
  • 回到这个问题有点晚了,但感谢你们的回答,@MarlinPierce 感谢你们“拼写出来”。丢失更新问题是我在哪里工作时最关心的问题。多个人可能已经为同一个资源加载了一个表单。或者更糟糕的是,打开同一资源的多个浏览器选项卡。我主要是在寻找避免破坏以前的更新的方法。请参阅下一条评论。
  • Rails 锁定使用每个资源(我们有数百个)额外的 db 字段似乎很笨拙。我有一个可能是幼稚的问题。这比简单地从表单(或任何数据)传递updated_at 属性更准确吗?我目前最简单的设计(我还没有实现)是覆盖所有活动记录的updated_at setter,并检查updated_at 进入(来自表单或其他)到updated_at 的值当前值,如果它更早,则通过错误。 @AndreasGebhard @MarlinPierce 你觉得这种方法有什么缺点吗?
【解决方案2】:

Rails 为您的问题提供了两种机制:乐观锁定和悲观锁定。就个人而言,我发现乐观的味道更容易使用。网上有很多文档,所以这只是一个starting point。它已经涵盖了大多数注意事项,例如多进程竞争条件和处理它们的隐藏表单字段!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-12-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-07
    • 2011-02-17
    相关资源
    最近更新 更多