简体   繁体   English

如何隐藏记录,而不是删除它们(从头开始软删除)

[英]How to hide records, rather than delete them (soft delete from scratch)

Let's keep this simple.让我们保持简单。 Let's say I have a User model and a Post model:假设我有一个User模型和一个Post模型:

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

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 .所以基本上他通过系统将帖子的deleted属性设置为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

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.虽然这可行,但我想在大量模型中实现上述内容,我不禁感到将上述比较复制+粘贴到我的所有模型中可能是 DRYer。 A lot dryer.很多烘干机。

I also think putting a deleted column in every single deletable model in my app feels a bit cumbersome too.我还认为在我的应用程序中的每个可删除模型中放置一个deleted列也有点麻烦。 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 

and then querying self.state.deleted in the comparator?然后在比较器中查询self.state.deleted 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.这仍然没有解决在我的模型中有一个非常非常相似的类方法来检查实例是否在显示内容之前被删除的问题。

In the deleted_by attribute, I'm thinking of placing the admin's id who deleted it.deleted_by属性中,我正在考虑放置删除它的管理员ID。 But what about when an admin undelete a post?但是当管理员取消删除帖子时呢? Maybe I should just have an edited_by id.也许我应该有一个edited_by id。

How do I set up a dependent: :destroy type relationship between the user and his posts?如何在用户和他的帖子之间建立一个dependent: :destroy类型关系? Because now I want to do this: dependent: :set_deleted_to_0 and I'm not sure how to do this.因为现在我想这样做: dependent: :set_deleted_to_0我不知道该怎么做。

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.此外,我们不只是想将帖子的已删除属性设置为 1,因为我们实际上想要更改我们的administrated_content发出的消息。 We now want it to say, This post has been removed because of its user has been deleted .我们现在要说的是, 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.我敢肯定我可以跳进去做一些hacky的事情,但我想从一开始就正确地做。

I also try to avoid gems when I can because I feel I'm missing out on learning.我也尽量避免宝石,因为我觉得我错过了学习。

I usually use a field named deleted_at for this case:在这种情况下,我通常使用名为deleted_at的字段:

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
  # ...

Want to share this functionnality between multiple models?想要在多个模型之间共享此功能?

=> Make an extension of it! => 扩展它!

# 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

Usage:用法:

class Post < ActiveRecord::Base
  act_as_fake_deletable

Overwriting the defaults:覆盖默认值:

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

Boom!繁荣! Done.完毕。

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.警告:该模块会覆盖 ActiveRecord 的destroydelete方法,这意味着您将无法再使用这些方法销毁您的记录。 Instead of overwriting you could create a new method, named soft_destroy for example.您可以创建一个名为soft_destroy的新方法,而不是覆盖。 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.因此,在您的应用程序(或控制台)中,您将在相关时使用soft_destroy ,并在您真正想要“硬销毁”记录时使用destroy / delete方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM