构建 '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 %>