15

レコードをソフト削除し、検索中に検索やその他の条件で表示しないようにする必要があるモデルが必要です。

レコードを削除せずにモデルを保持したい。これについてどうすればいいですか?

4

11 に答える 11

23

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

削除可能

ソフト削除を追加するためのレールの懸念。

カスタマイズ/変更するための非常にシンプルで柔軟な方法

(delete 列をタイムスタンプに変更し、メソッドを変更して 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 を使用してフォーカスするか、タイムスタンプ列を使用する場合は touch を使用します。


編集済み

rails <= 3.x を使用している場合 (この例では boolean の代わりに DateTime フィールドも使用しています)、いくつかの違いがあります。

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
于 2014-11-10T23:43:52.010 に答える
19

この宝石を試してみてください: https://github.com/technoweenie/acts_as_paranoid - 実際にレコードを削除せずにレコードを非表示にして復元できる ActiveRecord プラグイン

于 2012-10-05T06:16:07.653 に答える
9

deletedorと呼ばれるブールフィールドを追加するだけで、その効果が得られます。レコードをソフト削除するときは、そのフィールドを true に設定するだけです。

検索を行うときは、それを条件として追加するだけです(またはスコープを作成します)。

于 2012-10-05T06:06:17.143 に答える
5

default_scopeActiveRecord 3の機能はこれを簡単にしますが、個人的には、プロジェクトにドロップできる幅広い標準ソリューションを好みます。acts_as_archive特に、アクセス頻度の低い削除されたレコードを別のテーブルに移動し、ベース テーブルを小さくしてデータベース サーバーの RAM に保持できるため、ほとんどのプロジェクトに最適です。

必要に応じて、ソフト削除の代わりにバージョン管理を検討することもできます。

于 2012-10-05T06:15:27.923 に答える
3

このようなモジュールを定義できます

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に設定するために呼び出して、それを回復することができます。

于 2016-06-01T00:20:23.653 に答える
1

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
于 2013-03-21T14:24:14.670 に答える
0

すべてのレコードを取得したい場合は、デフォルトのスコープの原因を使用しません。「with_exclusive_scope」を使用して無視する必要があります。これは面倒です。これは、レコードが削除されたときに設定される「deleted」ブールフィールドを追加することによって行われます。また、条件に従ってデータを取得するためのスコープを追加します。

チェックアウトRailsdefault_scopeとRailsのオーバーライド:with_exclusive_scopeが保護されているのはなぜですか?それを使用する方法について何か良い習慣はありますか?

于 2012-10-05T06:55:28.110 に答える
0

テーブルに status という列を 1 つ追加し、レコードを削除すると、列の status の値が非アクティブに更新されます。レコードのフェッチ中に、条件ステータス != "非アクティブ" をクエリに追加します。

于 2012-10-05T13:51:26.617 に答える
0

Rails 4 の場合は act_as_paranoid を使用せず (Rails 4 のバグ)、paranoiaを使用します。必要なことは、deleted_at タイムスタンプ列を追加し、acts_as_paranoia をモデルに含めることだけです。

そこからdestroy、オブジェクトを呼び出すだけで、他のすべての ActiveRecord リレーションと他のほとんどのメソッド (:count など) が自動的に soft_deleted レコードを除外します。

于 2015-12-02T20:05:58.550 に答える