[英]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
# ...
# 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 的
destroy
和delete
方法,这意味着您将无法再使用这些方法销毁您的记录。 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.