【问题标题】:How to hide records, rather than delete them (soft delete from scratch)如何隐藏记录,而不是删除它们(从头开始软删除)
【发布时间】:2014-05-25 20:57:45
【问题描述】:

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

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

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。但是当管理员取消删除帖子时呢?也许我应该有一个edited_by id。

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

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

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

【问题讨论】:

  • 我通常使用一个字段deleted_at,它是一个日期时间字段。当该字段完成时,表示该记录已被删除(类似于布尔值,但提供日期)
  • 看看 paranoia gem -> github.com/radar/paranoia

标签: ruby-on-rails


【解决方案1】:

在这种情况下,我通常使用一个名为 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
  # ...

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

=> 做一个扩展!

# 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 方法。

【讨论】:

  • 如果 OP 在很多模型中都需要这个,就像帖子中暗示的那样,关注将是一个很好的地方。
  • @MichaelKohl 我想要一个这样的例子
  • 例如,我用模块 @Starkers 更新了我的答案
  • 好的,这很酷,但我不想使用范围来不加载已删除的记录。我想显示记录,但更改显示的内容。所以通常会显示'self.content',但现在会显示'this has been deleted by admin x'。我怎么能把它写成一个模块?我不得不说我想覆盖什么属性。
  • 另外,当 ActiveRecord::Base 被扩展以包含模块时,为什么我需要在我的模型中放置 act_as_fake_deletable(从底部开始的第三行 - 顺便说一句,我只有一个简单的模块尝试!)?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多