让我们保持这个简单。我们假设我有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,但我想从一开始就做好。
我尽可能地避免使用宝石,因为我觉得我错过了学习。
答案 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
# ...
# 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的destroy
和delete
方法,这意味着您将无法再使用这些方法销毁记录。您可以创建一个名为soft_destroy
的新方法,而不是覆盖。因此,在您的应用(或控制台)中,您可以在相关时使用soft_destroy
,并在您真正想要“破坏”记录时使用destroy
/ delete
方法。