Как упростить процесс мягкого удаления с помощью Ruby on Rails?

Я хочу иметь модель, где мне нужно мягко удалить запись, а не показывать ее в поиске или любых других условиях во время поиска.

Я хочу сохранить модель без удаления записи. Как это сделать?

Ответ 1

Попробуйте этот камень: https://github.com/technoweenie/acts_as_paranoid - плагин ActiveRecord, позволяющий скрывать и восстанавливать записи, не удаляя их фактически

Ответ 2

Просто используйте беспокойство в рельсах 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

нестираемой

Рельсы касаются добавления мягких удалений.

Очень простой и гибкий способ настройки/изменения

(Вы можете изменить столбец удаления на временную метку и изменить методы для вызова ActiveRecord touch).

Лучше всего, когда вы хотите контролировать код, не имеющий гемов для простых задач.

использование

В ваших таблицах добавьте логический столбец 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 <= 3.x (в этом примере вместо логического поля также используется поле 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 delete_at

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

Ответ 3

Просто добавьте логическое поле под названием deleted или что-то в этом роде. Когда вы мягко удаляете запись, просто установите для этого поля значение true.

При выполнении поиска просто добавьте это как условие (или создайте область для него).

Ответ 4

Функциональность default_scope в ActiveRecord 3 делает это легко, но лично я предпочитаю широкий спектр стандартных решений, которые можно отбросить в проект. acts_as_archive, в частности, наилучшим образом подходит для большинства моих проектов, поскольку он перемещает редкие записи удаленных записей в отдельную таблицу, позволяя базовой таблице оставаться маленькой и в ОЗУ сервера базы данных.

В зависимости от ваших потребностей вы также можете рассмотреть versioning вместо мягкого удаления.

Ответ 5

  • Добавить поле даты в вашу модель - deleted_at.
  • Отмените метод delete (или destroy) на вашей модели, чтобы установить значение deleted_at. Вы также можете создать его как новый метод. Что-то вроде soft_delete.
  • Добавьте метод restore/undelete к вашей модели, чтобы вернуть значение deleted_at в значение null.
  • Необязательно: создайте метод псевдонима для исходного метода удаления (или уничтожения). Назовите его что-то вроде 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 и восстановить его.

Ответ 7

Посмотрите rails3_acts_as_paranoid.

Простой плагин, который скрывает записи вместо их удаления, будучи способные восстановить их.

...

Этот плагин был вдохновлен acts_as_paranoid и acts_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, попробуйте этот камень: https://github.com/alfa-jpn/kakurenbo

Функция ассоциации Какуренбо лучше, чем другие драгоценные камни.

Ответ 9

Я не буду использовать причину по умолчанию, если я хочу получить все записи, мне нужно игнорировать его с помощью "with_exclusive_scope", который, в свою очередь, беспорядочен. это может быть связано с добавлением "удаленного" логического поля, которое устанавливается, когда запись удаляется. Кроме того, было бы добавлено области для получения данных в соответствии с условием.

checkout Переопределение Rails default_scope и Rails: Почему защита с_exclusive_scope защищена? Любая хорошая практика по ее использованию?

Ответ 10

Добавьте один столбец, указывающий статус в вашей таблице, и при удалении записей обновите значение статуса столбца до неактивного. и, извлекая записи, добавьте условие condition!= "inactive" в запросе.

Ответ 11

Для Rails 4 не используйте act_as_paranoid (багги для Rails 4), используйте paranoia. Все, что вам нужно сделать, это добавить столбец timestamp deleted_at и включить act_as_paranoia в модель.

Оттуда просто вызовите destroy на объект, а все остальные отношения ActiveRecord и большинство других методов (например: count) автоматически исключают записи soft_deleted.