构建 'editor approved' 版本用户生成内容的 Active Record 范围
Building an Active Record scope of 'editor approved' versions of user-generated content
我 运行 一个类似于 Whosebug 的站点,用户可以在其中发布内容 (Posts)。还有一个首页展示了我们的编辑精选的 Posts。
我想允许我们的用户随时编辑他们的 Post,同时仍然确保在我们的编辑首先审查之前不会在我们的首页上显示任何内容。
为了解决这个问题,我想我们可以实现一个像 paper_trail
这样的版本控制系统,并在我们使用 Post 时存储当前的 version_id
。通过这种方式,我们可以简单地在首页上获取编辑审阅的内容,同时仍然在网站的不太重要的部分(例如用户配置文件)上显示最新版本。我们的编辑可以定期审查任何更改并批准这些更改。
我想知道最干净的方法是什么,它允许我在某些控制器中 select 审查版本并在其他控制器中审查最新版本,同时保持共享接口和最少的重复代码。
理想的解决方案将涉及 reviewed
范围,这样我就可以简单地 Post.reviewed
并取回已审核的版本,然后 Post.all
获取最新版本.
虽然我不确定如何解决这个问题,因为获得审查版本需要反序列化 PaperTrail 存储的对象 (version.reify
),而这在一个范围内似乎是不可能的。
我可以像这样使用 class 方法:
def self.reviewed
all.map do |post|
Version.find(post.version_id).reify
end.compact! || Post.none
end
然而,这并不理想,因为它不是一个真正的作用域,因此不可链接等等。
或者我可以使用 Post 实例方法,例如:
def reviewed_version
Version.find(version_id).reify
end
这在理论上可行,但这意味着我现在必须在我的所有视图中调用此方法,而控制器负责获取正确的数据。假设我在首页和用户个人资料上都有一个 render collection: @posts
,我的 _post.html.erb
部分如何知道是否调用 reviewed_version
。
我不喜欢 PaperTrail,但它有很多我不想重复的优点。但是,如果 PaperTrail 被证明太不灵活,我愿意探索其他方向。
FWIW,我也看过 Draftsman,但它认为 'draft' 是例外(即在大多数情况下你不会显示草稿),而在我的情况下我想在大多数页面上显示最新版本,除了一些像首页这样的特定页面。
对于做一些本身很简单的事情来说,这听起来过于复杂了:
- 您有一个
Post
模型,其中包含公共属性(如作者和创建日期)
Post has_many :revisions
- 一个
Revision
有一个reviewed
布尔属性
如果您想要查找 Post
的最后审阅修订版,您可以:
class Post < ActiveRecord::Base
has_many :revisions
delegate :content, to: :current_revision # you can even have those to transparently delegate
def current_revision
@current_revision ||= revisions.where( reviewed: true ).order( "created_at desc" ).first
end
end
如果您只想检索 Post
已审阅修订的内容:
class Post < ActiveRecord::Base
has_many :revisions
scope :reviewed, ->{ group( 'posts.id' ) ).having( 'count(revisions.id) > 0' ).joins( :revisions ).where( "revisions.reviewed = ?", true ) }
end
这就是我的做法,让更熟悉paper_trail
的人告诉你它的方法:)
编辑
正如评论中所讨论的,装饰器还可以帮助区分修订和委托方法,同时仍然能够传递 activerecord 关系。以这种方式给出一些东西:
class Post < ActiveRecord::Base
has_many :revisions
scope :reviewed, ->{ group( 'posts.id' ) ).having( 'count(revisions.id) > 0' ).joins( :revisions ).where( "revisions.reviewed = ?", true ) }
def reviewed_revision
@reviewed_revision ||= revisions.where( reviewed: true ).order( "created_at desc" ).first
end
def latest_revision
@latest_revision ||= revisions.order( "created_at desc" ).first
end
end
class PostDecorator
attr_reader :post, :revision
delegate :content, :updated_at, to: :revision
def self.items( scope, revision_method )
revision_method = :reviewed_revision unless %i[ reviewed_revision latest_revision ].include?( revision_method )
scope.map do |post|
PostDecorator.new( post, post.send( revision_method ) )
end
end
def initalize( post, revision )
@post, @revision = post, revision
end
def method_missing( action_name, *args, &block )
post.send( action_name, *args, &block )
end
end
# view
#
# <% PostDecorator.items( Post.reviewed.limit(10), :reviewed_revision ).each do |post| %>
# <p><%= post.author.name %></p>
# <p><%= simple_format post.content %></p>
# <% end %>
我 运行 一个类似于 Whosebug 的站点,用户可以在其中发布内容 (Posts)。还有一个首页展示了我们的编辑精选的 Posts。
我想允许我们的用户随时编辑他们的 Post,同时仍然确保在我们的编辑首先审查之前不会在我们的首页上显示任何内容。
为了解决这个问题,我想我们可以实现一个像 paper_trail
这样的版本控制系统,并在我们使用 Post 时存储当前的 version_id
。通过这种方式,我们可以简单地在首页上获取编辑审阅的内容,同时仍然在网站的不太重要的部分(例如用户配置文件)上显示最新版本。我们的编辑可以定期审查任何更改并批准这些更改。
我想知道最干净的方法是什么,它允许我在某些控制器中 select 审查版本并在其他控制器中审查最新版本,同时保持共享接口和最少的重复代码。
理想的解决方案将涉及 reviewed
范围,这样我就可以简单地 Post.reviewed
并取回已审核的版本,然后 Post.all
获取最新版本.
虽然我不确定如何解决这个问题,因为获得审查版本需要反序列化 PaperTrail 存储的对象 (version.reify
),而这在一个范围内似乎是不可能的。
我可以像这样使用 class 方法:
def self.reviewed
all.map do |post|
Version.find(post.version_id).reify
end.compact! || Post.none
end
然而,这并不理想,因为它不是一个真正的作用域,因此不可链接等等。
或者我可以使用 Post 实例方法,例如:
def reviewed_version
Version.find(version_id).reify
end
这在理论上可行,但这意味着我现在必须在我的所有视图中调用此方法,而控制器负责获取正确的数据。假设我在首页和用户个人资料上都有一个 render collection: @posts
,我的 _post.html.erb
部分如何知道是否调用 reviewed_version
。
我不喜欢 PaperTrail,但它有很多我不想重复的优点。但是,如果 PaperTrail 被证明太不灵活,我愿意探索其他方向。
FWIW,我也看过 Draftsman,但它认为 'draft' 是例外(即在大多数情况下你不会显示草稿),而在我的情况下我想在大多数页面上显示最新版本,除了一些像首页这样的特定页面。
对于做一些本身很简单的事情来说,这听起来过于复杂了:
- 您有一个
Post
模型,其中包含公共属性(如作者和创建日期) Post has_many :revisions
- 一个
Revision
有一个reviewed
布尔属性
如果您想要查找 Post
的最后审阅修订版,您可以:
class Post < ActiveRecord::Base
has_many :revisions
delegate :content, to: :current_revision # you can even have those to transparently delegate
def current_revision
@current_revision ||= revisions.where( reviewed: true ).order( "created_at desc" ).first
end
end
如果您只想检索 Post
已审阅修订的内容:
class Post < ActiveRecord::Base
has_many :revisions
scope :reviewed, ->{ group( 'posts.id' ) ).having( 'count(revisions.id) > 0' ).joins( :revisions ).where( "revisions.reviewed = ?", true ) }
end
这就是我的做法,让更熟悉paper_trail
的人告诉你它的方法:)
编辑
正如评论中所讨论的,装饰器还可以帮助区分修订和委托方法,同时仍然能够传递 activerecord 关系。以这种方式给出一些东西:
class Post < ActiveRecord::Base
has_many :revisions
scope :reviewed, ->{ group( 'posts.id' ) ).having( 'count(revisions.id) > 0' ).joins( :revisions ).where( "revisions.reviewed = ?", true ) }
def reviewed_revision
@reviewed_revision ||= revisions.where( reviewed: true ).order( "created_at desc" ).first
end
def latest_revision
@latest_revision ||= revisions.order( "created_at desc" ).first
end
end
class PostDecorator
attr_reader :post, :revision
delegate :content, :updated_at, to: :revision
def self.items( scope, revision_method )
revision_method = :reviewed_revision unless %i[ reviewed_revision latest_revision ].include?( revision_method )
scope.map do |post|
PostDecorator.new( post, post.send( revision_method ) )
end
end
def initalize( post, revision )
@post, @revision = post, revision
end
def method_missing( action_name, *args, &block )
post.send( action_name, *args, &block )
end
end
# view
#
# <% PostDecorator.items( Post.reviewed.limit(10), :reviewed_revision ).each do |post| %>
# <p><%= post.author.name %></p>
# <p><%= simple_format post.content %></p>
# <% end %>