ruby-on-rails - 如何使用 Ruby on Rails 简化软删除过程?

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

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

我想保留模型而不删除记录。如何解决这个问题?

最佳答案

只需在 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。

用法

在您的表中添加一个 bool 列 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(此示例还使用 DateTime 字段而不是 bool 值),则存在一些差异:

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

关于ruby-on-rails - 如何使用 Ruby on Rails 简化软删除过程?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12740397/

相关文章:

ruby-on-rails - Rails 请求范围内的单例

ruby-on-rails - Rails : request. remote_id 返回六位数字

ruby-on-rails - rails : Trying to render image of users with same attribute

javascript - 多日期选择器错误?

mysql - RoR中的动态条件查询

mysql - 如何在Rails中仅从mysql的ActiveRecord集合中删除对象?

sql - 将转储/sql 文件导入我在 Linode 上的 postgresql 数据库

ruby-on-rails - 尝试为 Rails 中的用户名验证编写 REGEX

ruby-on-rails-3 - 在设备的 :authenticate_user? 之后添加过滤器

MySQL Ruby Gem 安装问题