📌  相关文章
📜  rails 从 db 中破坏某些东西 (1)

📅  最后修改于: 2023-12-03 15:19:41.970000             🧑  作者: Mango

Rails 从数据库中破坏某些东西

在 Rails 应用程序中,有时需要从数据库中删除或破坏某些数据。这可能涉及到多个模型,与许多依赖关系。

在本文中,我们将讨论如何以一种有效和简单的方式处理这种情况。

损坏 vs 删除数据

在 Rails 中,有两种主要的方式来处理不再需要的数据:

  • 删除数据意味着永久删除与该记录关联的所有内容。这意味着,当您删除数据时,您将无法恢复与该记录相关的任何模型。这可能会引起数据完整性问题和其他相关的问题。
  • 破坏数据也是从数据库中移除数据的一种方法,但与删除记录不同,它保留了所有相关的模型和数据,只是将它们标记为已刪除。这也被称为“软删除”。

在这篇文章中,我们将着重介绍如何使用 Rails 中的破坏方法。

添加破坏的支持

要使某个模型支持破坏功能,首先需要向该模型添加一个布尔值列来表示该记录是否已被破坏。例如,如果您有一个名为 Event 的模型,并且希望支持软删除,则可以这样添加一个名为 deleted 的布尔列:

class AddDeletedToEvents < ActiveRecord::Migration[6.0]
  def change
    add_column :events, :deleted, :boolean, default: false
  end
end

接下来,您需要定义一个 destroy 方法,以便它将记录标记为已刪除而不是从数据库中永久删除。对于 Event 模型,它可以像这样:

class Event < ApplicationRecord
  def destroy
    update(deleted: true)
  end
end

现在,您可以在 Event 实例上调用 destroy 方法,而不会在数据库中删除记录。相反,它会标记为已刪除。

处理模型之间的依赖

但是这种方法有一个问题:如果您有多个相互关联的模型,那么破坏一个模型的记录可能会影响与其相关的其他模型。例如,如果您有一个名为 User 的模型,还有一个名为 Post 的模型,其中 User 拥有很多 Post,并且您在删除一个 User 记录时也想删除所有的 Post 记录,那该怎么办?

一种解决方案是使用 Rails 的 dependent: :destroy 选项来自动删除所有相关的记录。但是在软删除的情况下,我们仍需要标记这些相关的记录。

处理依赖关系

第一种解决方法是在父模型上覆盖 destroy 方法,然后逐个破坏子记录。例如,对于上面的 UserPost 的例子,可以这样:

class User < ApplicationRecord
  has_many :posts, dependent: :destroy

  def destroy
    posts.each(&:destroy)
    super
  end
end

class Post < ApplicationRecord
  def destroy
    update(deleted: true)
  end
end

这个 User 模型的 destroy 方法首先遍历它的所有 posts,并将每个 post 都标记为已刪除。然后,它调用原始的 destroy 方法,这将标记 User 记录为已刪除。

这种方法的一个问题是对于每个相关的模型都要增加破坏的支持,这可能很繁琐。

使用 ActiveRecord 记录器

另一种更简单的方法是使用 ActiveRecord 记录器来记录模型之间的依赖关系。这是一个 Rails 内置功能,可用于记录 ActiveRecord 实例的更改。我们可以使用它来记录删除或软删除。

假设您有一个名为 Post 的模型,以及一个名为 Comment 的模型,其中 Post 有很多 comment。您希望在软删除Post时破坏所有 comment

首先,需要在 Post 中定义依赖关系:

class Post < ActiveRecord::Base
  has_many :comments
  has_many :destroyed_comments, -> { where(deleted: true) }, class_name: 'Comment', dependent: nil
end

Ok,接下来,我们利用 ActiveRecord 记录器来记录对 post 的更改。

class Post < ActiveRecord::Base
  has_many :comments
  has_many :destroyed_comments, -> { where(deleted: true) }, class_name: 'Comment', dependent: nil

  around_destroy :record_destroyed_comments

  private

  def record_destroyed_comments
    destroyed_associations = if destroyed?
                               # 如果 Post 已经被删除,我们不需要记录破坏的评论
                               []
                             else
                               # 记录将破坏的评论
                               comments.select(&:should_destroy?)
                             end
    yield
    destroyed_associations.each(&:destroy)
  end
end

我们在 Post 中定义了一个私有方法 record_destroyed_comments,它记录应破坏的评论。我们将它作为 around_destroy 的回调来使用,以便在简历更改之前和之后运行它。在简历更改后,我们可以使用记录器来找到将要破坏的评论并摧毁它们。

Comment 模型中,需要重写 destroy 方法。

class Comment < ActiveRecord::Base
  belongs_to :post
  scope :deleted, -> { where(deleted: true) }

  def destroy
    return super if deleted?
    update(deleted: true)
  end

  def should_destroy?
    deleted? || post.destroyed?
  end
end

我们已经完成了处理依赖的方法,当我们调用 post.destroy 时,他会将自己软删除,同时破坏所有的 comment。同时,我们还可以使用Bull的简单框架来执行定时任务删除所有已删除的软删除模型。

class CleanUp
  include Bull::Job
  cron '0 4 * * *'

  def perform
    [Event, User, Post, Comment].each do |model|
      model.where(deleted: true).find_each(&:destroy)
    end
  end
end
结论

本文介绍了在 Rails 应用程序中处理软删除的最佳实践。我们展示了如何使用记录器来处理模型之间的依赖,并提供了处理模型清理的基本工具。现在,您可以使用这些技巧来管理您的数据,从而实现更好的性能和安全性。