【问题标题】:How to simplify the soft delete process with Ruby on Rails?如何使用 Ruby on Rails 简化软删除过程?
【发布时间】:2012-09-26 06:20:33
【问题描述】:

我想要一个模型,我需要软删除记录,而不是在搜索时在查找或任何其他条件中显示它们。

我想保留模型而不删除记录。这个怎么办?

【问题讨论】:

  • 模型和记录是完全不同的概念。 model 是模板,而 record 是它的实际实例。
  • 我知道这一点。我想简化特定模型的整个过程 - 这意味着每当我软删除一条记录时,我不想在 Model.all 或任何其他搜索查询中看到它,除非我指定它。

标签: ruby-on-rails ruby ruby-on-rails-3


【解决方案1】:

只需在 Rails 4 中使用关注点

此处为示例

module SoftDeletable
  extend ActiveSupport::Concern


  included do
    default_scope { where(is_deleted: false) }
    scope :only_deleted, -> { unscope(where: :is_deleted).where(is_deleted: true) }
  end

  def delete
    update_column :is_deleted, true if has_attribute? :is_deleted
  end

  def destroy;
    callbacks_result = transaction do
      run_callbacks(:destroy) do
        delete
      end
    end
    callbacks_result ? self : false
  end

  def self.included(klazz)
    klazz.extend Callbacks
  end

  module Callbacks
    def self.extended(klazz)
      klazz.define_callbacks :restore
      klazz.define_singleton_method("before_restore") do |*args, &block|
        set_callback(:restore, :before, *args, &block)
      end
      klazz.define_singleton_method("around_restore") do |*args, &block|
        set_callback(:restore, :around, *args, &block)
      end
      klazz.define_singleton_method("after_restore") do |*args, &block|
        set_callback(:restore, :after, *args, &block)
      end
    end
  end

  def restore!(opts = {})
    self.class.transaction do
      run_callbacks(:restore) do
        update_column :is_deleted, false
        restore_associated_records if opts[:recursive]
      end
    end
    self
  end

  alias :restore :restore!

  def restore_associated_records
    destroyed_associations = self.class.reflect_on_all_associations.select do |association|
      association.options[:dependent] == :destroy
    end
    destroyed_associations.each do |association|
      association_data = send(association.name)
      unless association_data.nil?
        if association_data.is_deleted?
          if association.collection?
            association_data.only_deleted.each { |record| record.restore(recursive: true) }
          else
            association_data.restore(recursive: true)
          end
        end
      end
      if association_data.nil? && association.macro.to_s == 'has_one'
        association_class_name = association.options[:class_name].present? ? association.options[:class_name] : association.name.to_s.camelize
        association_foreign_key = association.options[:foreign_key].present? ? association.options[:foreign_key] : "#{self.class.name.to_s.underscore}_id"
        Object.const_get(association_class_name).only_deleted.where(association_foreign_key, self.id).first.try(:restore, recursive: true)
      end
    end
    clear_association_cache if destroyed_associations.present?
  end
end

可删除

添加软删除的 Rails 关注点。

非常简单灵活的自定义/更改方式

(您可以将删除列更改为时间戳并更改调用ActiveRecord的方法touch)。

最好在你想控制代码的地方没有简单任务的 gem。

用法

在您的表格中添加一个布尔列 is_deletable

class AddDeletedAtToUsers < ActiveRecord::Migration
  def change
    add_column :users, :is_deleted, :boolean
  end
end

在您的模型中

class User < ActiveRecord::Base
  has_many :user_details, dependent: :destroy

  include SoftDeletable
end

可用的方法和回调:

User.only_deleted
User.first.destroy
User.first.restore
User.first.restore(recursive: true)

注意: 如果您决定使用时间戳列,请使用 update_column 或触摸。


已编辑

如果您使用的是 rails

module SoftDeletable
  extend ActiveSupport::Concern

  included do
    default_scope { where(deleted_at: nil }
    # In Rails <= 3.x to use only_deleted, do something like 'data = Model.unscoped.only_deleted'
    scope :only_deleted, -> { unscoped.where(table_name+'.deleted_at IS NOT NULL') }
  end

  def delete
    update_column :deleted_at, DateTime.now if has_attribute? :deleted_at
  end

  # ... ... ...
  # ... OTHERS IMPLEMENTATIONS ...
  # ... ... ...

  def restore!(opts = {})
    self.class.transaction do
      run_callbacks(:restore) do
        # Remove default_scope. "UPDATE ... WHERE (deleted_at IS NULL)"
        self.class.send(:unscoped) do
          update_column :deleted_at, nil
          restore_associated_records if opts[:recursive]
        end
      end
    end
    self
  end

  alias :restore :restore!

  def restore_associated_records
    destroyed_associations = self.class.reflect_on_all_associations.select do |association|
      association.options[:dependent] == :destroy
    end
    destroyed_associations.each do |association|
      association_data = send(association.name)
      unless association_data.nil?
        if association_data.deleted_at?
          if association.collection?
            association_data.only_deleted.each { |record| record.restore(recursive: true) }
          else
            association_data.restore(recursive: true)
          end
        end
      end
      if association_data.nil? && association.macro.to_s == 'has_one'
        association_class_name = association.options[:class_name].present? ? association.options[:class_name] : association.name.to_s.camelize
        association_foreign_key = association.options[:foreign_key].present? ? association.options[:foreign_key] : "#{self.class.name.to_s.underscore}_id"
        Object.const_get(association_class_name).only_deleted.where(association_foreign_key, self.id).first.try(:restore, recursive: true)
      end
    end
    clear_association_cache if destroyed_associations.present?
  end
end

用法

在您的表格中添加一个 DateTime 列 deleted_at

class AddDeletedAtToUsers < ActiveRecord::Migration
  def change
    add_column :users, :deleted_at, :datetime
  end
end

【讨论】:

  • 我不得不稍微劫持你的代码才能真正摧毁一个对象。它也可以将它包含在 activemodel 中,然后让它决定是否可以执行 softdelete(在这种情况下需要使用默认范围)
【解决方案2】:

试试这个 gem:https://github.com/technoweenie/acts_as_paranoid - ActiveRecord 插件允许您隐藏和恢复记录而不实际删除它们

【讨论】:

  • 当我们可以处理 Rails 问题的单文件任务时,我总是避免使用 gem。
  • 在下面查看我对 rails 问题的回答
  • @Abs 你能详细说明为什么吗?无论文件数量如何,为什么首选重新实现功能并不是很明显......
  • Here 是一个类似 gem 的列表。
  • 这将是另一个依赖,但至少它会是一个经过测试的依赖。
【解决方案3】:

只需添加一个名为deleted 的布尔字段或类似的东西。当您软删除记录时,只需将该字段设置为 true。

在进行查找时,只需将其添加为条件(或为其设置范围)。

【讨论】:

    【解决方案4】:

    ActiveRecord 3 中的default_scope 功能使这很容易,但就个人而言,我更喜欢可以放入项目中的标准解决方案wide varietyacts_as_archive 尤其适合我的大多数项目,因为它将不经常访问的已删除记录移动到单独的表中,从而使基表保持较小并位于数据库服务器的 RAM 中。

    根据您的需要,您可能还需要考虑versioning 而不是软删除。

    【讨论】:

      【解决方案5】:
      1. 向模型添加日期字段 - deleted_at。
      2. 覆盖模型上的删除(或销毁)方法以设置 deleted_at 值。您也可以将其创建为新方法。类似于 soft_delete。
      3. 向您的模型添加恢复/取消删除方法以将 deleted_at 值设置回 null。
      4. 可选:为原始删除(或销毁)方法创建别名方法。将其命名为 hard_delete。

      【讨论】:

        【解决方案6】:

        你可以这样定义一个模块

        module ActiveRecordScope
          def self.included(base)
            base.scope :not_deleted, -> { base.where(deleted: false) }
            base.send(:default_scope) { base.not_deleted }
            base.scope :only_deleted, -> { base.unscope(where: :deleted).where(deleted: true) }
        
            def delete
              update deleted: true
            end
        
            def recover
              update deleted: false
            end
          end
        end
        

        然后在你的课堂上,你可以写这样的东西:

        class User < ActiveRecord::Base
          include ActiveRecordScope
        
        end
        

        所以你有软删除和恢复。

        您致电user.delete 软删除用户。然后你可以调用user.recover将删除的重新设置为false,并恢复它。

        【讨论】:

        • 不会覆盖destroy方法
        • 正确。但是你也可以在模块中添加方法destroy。
        【解决方案7】:

        看看rails3_acts_as_paranoid

        一个隐藏记录而不是删除记录的简单插件,被 能够恢复它们。

        ...

        这个插件的灵感来自acts_as_paranoidacts_as_active

        用法:

        class Paranoiac < ActiveRecord::Base
            acts_as_paranoid
            scope :pretty, where(:pretty => true)
        end
        
        Paranoiac.create(:pretty => true)
        
        Paranoiac.pretty.count #=> 1
        Paranoiac.only_deleted.count #=> 0
        Paranoiac.pretty.only_deleted.count #=> 0
        
        Paranoiac.first.destroy
        
        Paranoiac.pretty.count #=> 0
        Paranoiac.only_deleted.count #=> 1
        Paranoiac.pretty.only_deleted.count #=> 1
        

        【讨论】:

          【解决方案8】:

          如果你使用 Rails4,试试这个 gem:https://github.com/alfa-jpn/kakurenbo

          Kakurenbo的关联功能比其他宝石要好。

          【讨论】:

            【解决方案9】:

            如果我想获取所有记录,我不会使用默认范围,我需要使用“with_exclusive_scope”忽略它,这反过来又很混乱。这将通过添加一个“已删除”布尔字段来设置,该字段是在删除记录时设置的。此外,将根据条件添加范围以获取数据。

            结帐Overriding a Rails default_scopeRails: Why is with_exclusive_scope protected? Any good practice on how to use it?

            【讨论】:

              【解决方案10】:

              在表中添加一列表示状态,并在删除记录时将列状态的值更新为非活动状态。 并在获取记录时,在查询中添加条件 status != "inactive"。

              【讨论】:

                【解决方案11】:

                对于 Rails 4,不要使用acts_as_paranoid(Rails 4 有问题),使用paranoia。您所要做的就是添加一个deleted_at 时间戳列并在模型中包含acts_as_paranoia。

                从那里,只需在对象上调用destroy,所有其他 ActiveRecord 关系和大多数其他方法(如 :count)会自动排除 soft_deleted 记录。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2014-01-07
                  • 1970-01-01
                  • 2014-08-30
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多