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

让我们保持这个简单。 假设我有一个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 

现在,让我们说管理员想要“删除”(隐藏)post。 所以基本上他通过系统将post的deleted属性设置为1 。 我现在应该如何在视图中显示此post? 我应该在post上创建一个虚拟属性,如下所示:

 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。 很多烘干机。

我还认为在我的应用程序中的每个可删除模型中添加一个deleted列感觉有点麻烦。 我觉得我应该有一个’州’表。 你对此有何看法:

 class State #id:integer #deleted:boolean #deleted_by:integer belongs_to :user belongs_to :post end 

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

deleted_by属性中,我正在考虑放置删除它的管理员ID。 但是当管理员取消删除post时呢? 也许我应该有一个edited_by id。

如何在用户和他的post之间设置dependent: :destroy类型关系? 因为现在我想这样做: dependent: :set_deleted_to_0 ,我不知道该怎么做。

此外,我们不只是想将post的已删除属性设置为1,因为我们实际上想要更改administrated_content给出的消息。 我们现在想要它说, This post has been removed because of its user has been deleted 。 我确信我可以跳进去做一些hacky,但我想从一开始就做好。

我尽可能地避免使用gem,因为我觉得我错过了学习。

在这种情况下,我通常使用名为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_attributes(deleted_at: DateTime.current) end def delete destroy end def deleted? self.deleted_at.present? end # ... 

想在多个模型之间分享这个function吗?

=>进行扩展!

 # lib/extensions/act_as_fake_deletable.rb module ActAsFakeDeletable # override the model actions def destroy self.update_attributes(deleted_at: DateTime.current) end def delete self.destroy end def undestroy # to "restore" the file self.update_attributes(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方法。