如何隐藏记录,而不是删除它们(从头开始软删除) [英] How to hide records, rather than delete them (soft delete from scratch)

查看:43
本文介绍了如何隐藏记录,而不是删除它们(从头开始软删除)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

让我们保持简单.假设我有一个 User 模型和一个 Post 模型:

Let's keep this simple. Let's say I have a User model and a Post model:

class User < ActiveRecord::Base
    # id:integer name:string deleted:boolean

    has_many :posts       
end

class Post < ActiveRecord::Base
    # id:integer user_id:integer content:string deleted:boolean

    belongs_to :user
end

现在,假设管理员想要删除"(隐藏)帖子.所以基本上他通过系统将帖子的deleted 属性设置为1.我现在应该如何在视图中显示这篇文章?我应该像这样在帖子上创建一个虚拟属性:

Now, let's say an admin wants to "delete" (hide) a post. So basically he, through the system, sets a post's deleted attribute to 1. How should I now display this post in the view? Should I create a virtual attribute on the post like this:

class Post < ActiveRecord::Base
    # id:integer user_id:integer content:string deleted:boolean

    belongs_to :user

    def administrated_content
       if !self.deleted
          self.content
       else
          "This post has been removed"
       end
    end
end

虽然这行得通,但我想在大量模型中实现上述内容,我不禁觉得将上述比较复制粘贴到我的所有模型中可能是 DRYer.很多烘干机.

While that would work, I want to implement the above in a large number of models, and I can't help feeling that copy+pasting the above comparative into all of my models could be DRYer. A lot dryer.

我还认为在我的应用程序中的每个可删除模型中放置一个 deleted 列也感觉有点麻烦.我觉得我应该有一个状态"表.您对此有何看法:

I also think putting a deleted column in every single deletable model in my app feels a bit cumbersome too. I feel I should have a 'state' table. What are your thoughts on this:

class State
    #id:integer #deleted:boolean #deleted_by:integer

    belongs_to :user
    belongs_to :post
end 

然后在比较器中查询self.state.deleted?这需要多态表吗?我只尝试过一次多态,但我无法让它工作.(记住,这是一个非常复杂的自我参照模型).这仍然没有解决在我的模型中有一个非常非常相似的类方法来在显示内容之前检查实例是否被删除的问题.

and then querying self.state.deleted in the comparator? Would this require a polymorphic table? I've only attempted polymorphic once and I couldn't get it to work. (it was on a pretty complex self-referential model, mind). And this still doesn't address the problem of having a very, very similar class method in my models to check if an instance is deleted or not before displaying content.

deleted_by 属性中,我想放置删除它的管理员 ID.但是当管理员取消删除帖子时呢?也许我应该有一个 edited_by id.

In the deleted_by attribute, I'm thinking of placing the admin's id who deleted it. But what about when an admin undelete a post? Maybe I should just have an edited_by id.

如何在用户和他的帖子之间建立 dependent: :destroy 类型的关系?因为现在我想这样做:dependent: :set_deleted_to_0 但我不知道该怎么做.

How do I set up a dependent: :destroy type relationship between the user and his posts? Because now I want to do this: dependent: :set_deleted_to_0 and I'm not sure how to do this.

此外,我们不想简单地将帖子的已删除属性设置为 1,因为我们实际上想要更改我们的 administrated_content 发出的消息.我们现在想让它说,这篇文章已被删除,因为它的用户已被删除.我敢肯定我可以跳进去做一些黑客的事情,但我想从一开始就正确地做.

Also, we don't simply want to set the post's deleted attributes to 1, because we actually want to change the message our administrated_content gives out. We now want it to say, This post has been removed because of its user has been deleted. I'm sure I could jump in and do something hacky, but I want to do it properly from the start.

我也尽量避免宝石,因为我觉得我错过了学习.

I also try to avoid gems when I can because I feel I'm missing out on learning.

推荐答案

对于这种情况,我通常使用名为 deleted_at 的字段:

I usually use a field named deleted_at for this case:

class Post < ActiveRecord::Base
  scope :not_deleted, lambda { where(deleted_at: nil) }
  scope :deleted, lambda { where("#{self.table_name}.deleted_at IS NOT NULL") }

  def destroy
    self.update(deleted_at: DateTime.current)
  end

  def delete
    destroy
  end

  def deleted?
    self.deleted_at.present?
  end
  # ...


想要在多个模型之间共享此功能吗?

=>对其进行扩展!

# lib/extensions/act_as_fake_deletable.rb
module ActAsFakeDeletable
  # override the model actions
  def destroy
    self.update(deleted_at: DateTime.current)
  end

  def delete
    self.destroy
  end

  def undestroy # to "restore" the file
    self.update(deleted_at: nil)
  end

  def undelete
    self.undestroy
  end
  
  # define new scopes
  def self.included(base)
    base.class_eval do
      scope :destroyed, where("#{self.table_name}.deleted_at IS NOT NULL")
      scope :not_destroyed, where(deleted_at: nil)
      scope :deleted, lambda { destroyed }
      scope :not_deleted, lambda { not_destroyed }
    end
  end
end

class ActiveRecord::Base
  def self.act_as_fake_deletable(options = {})
    alias_method :destroy!, :destroy
    alias_method :delete!, :delete
    include ActAsFakeDeletable

    options = { field_to_hide: :content, message_to_show_instead: "This content has been deleted" }.merge!(options)

    define_method options[:field_to_hide].to_sym do
      return options[:message_to_show_instead] if self.deleted_at.present?
      self.read_attribute options[:field_to_hide].to_sym
    end
  end
end

用法:

class Post < ActiveRecord::Base
  act_as_fake_deletable

覆盖默认值:

class Book < ActiveRecord::Base
  act_as_fake_deletable field_to_hide: :title, message_to_show_instead: "This book has been deleted man, sorry!"

轰!完成.

警告:此模块覆盖 ActiveRecord 的 destroydelete 方法,这意味着您将无法使用这些方法销毁您的记录方法了.例如,您可以创建一个名为 soft_destroy 的新方法,而不是覆盖.因此,在您的应用程序(或控制台)中,您可以在相关时使用 soft_destroy 并在您真正想要删除"时使用 destroy/delete 方法.硬破坏"记录.

Warning: This module overwrite the ActiveRecord's destroy and delete methods, which means you won't be able to destroy your record using those methods anymore. Instead of overwriting you could create a new method, named soft_destroy for example. So in your app (or console), you would use soft_destroy when relevant and use the destroy/delete methods when you really want to "hard destroy" the record.

这篇关于如何隐藏记录,而不是删除它们(从头开始软删除)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆