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

时间:2014-04-11 15:45:39

标签: ruby-on-rails

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

现在,让我们说管理员希望&#34;删除&#34; (隐藏)一个帖子。所以基本上他通过系统将帖子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,但我想从一开始就做好。

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

1 个答案:

答案 0 :(得分:13)

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

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

=&GT;扩展它!

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