假设我有一个评论模型:
class Comment < ActiveRecord::Base
has_many :replies, class: "Comment", foreign_key: "reply_id"
end
我可以在视图中显示评论实例的回复:
comment.replies do |reply|
reply.content
end
但是,如何循环回复回复?它的回复?它的回复广告?我觉得我们需要通过类方法创建一个多维的回复数组,然后在视图中循环遍历这个数组。
我不想使用宝石,我想学习
答案 0 :(得分:8)
看起来你所拥有的只是距你想要的一步之遥。您只需要使用递归为每个回复调用相同的代码,因为您正在调用原始注释。 E.g。
<!-- view -->
<div id="comments">
<%= render partial: "comment", collection: @comments %>
</div>
<!-- _comment partial -->
<div class="comment">
<p><%= comment.content %></p>
<%= render partial: "comment", collection: comment.replies %>
</div>
注意:这不是最有效的做事方式。每次调用comment.replies时,活动记录都会运行另一个数据库查询。肯定有改进的余地,但无论如何这都是基本的想法。
答案 1 :(得分:3)
使用嵌套集仍然会从头开始算作&#39;?
嵌套集的简短描述是一种特定于数据库的策略,通过存储/查询订单前和订单后的树遍历计数来查询层次结构。
A picture is worth a thousand words(另请参阅the wikipedia page on nested sets)。
There are a bunch of nested set gems,我个人可以代表Awesome Nested Set和Ancestry
的质量发言然后,Awesome Nested Set(我从经验中知道,也可能是Ancestry)提供帮助程序来执行单个查询以提取树下的所有记录,并按排序的深度优先顺序遍历树,传入级别你去的时候。
Awesome Nested Set的视图代码如下:
<% Comment.each_with_level(@post.comments.self_and_descendants) do |comment, level| %>
<div style="margin-left: <%= level * 50 %>px">
<%= comment.body %>
<%# etc %>
</div>
<% end %>
我只是从模糊的回忆中做出来,并且已经有一段时间了,所以这就是它可以成为读者的一种练习&#34;
答案 2 :(得分:3)
我的方法是尽可能高效地完成这项工作。 首先介绍如何执行此操作:
考虑到这一点,我发现大多数人都会解决第一个问题,而不是第二个问题。所以让我们从容易的问题开始。 我们必须部分参与评论,以便引用jeanaux
的答案我们可以使用他的方法来显示评论,并在稍后的答案中更新
<!-- view -->
<div id="comments">
<%= render partial: "comment", collection: @comments %>
</div>
<!-- _comment partial -->
<div class="comment">
<p><%= comment.content %></p>
<%= render partial: "comment", collection: comment.replies %>
</div>
我们现在必须在一个查询中检索这些注释,以便我们可以在控制器中执行此操作。为了能够做到这一点,所有评论和回复都应该有一个commentable_id(并且如果是多态的则输入),这样当我们查询时我们可以获得所有评论,然后按照我们想要的方式对它们进行分组。
因此,如果我们有一个帖子,例如我们希望获得所有评论,我们将在控制器中说明。 @comments = @ post.comments.group_by {| c | c.reply_id}
通过这个我们在一个查询中有注释处理直接显示 现在我们可以这样做来显示它们而不是我们以前做过的事情
所有未回复的评论现在都在@comments [nil]中,因为它们没有reply_id (注意:我不喜欢@comments [nil]如果有人有任何其他建议请评论或编辑)
<!-- view -->
<div id="comments">
<%= render partial: "comment", collection: @comments[nil] %>
</div>
每条评论的所有回复都将在父评论ID
下的内容中<!-- _comment partial -->
<div class="comment">
<p><%= comment.content %></p>
<%= render partial: "comment", collection: @comments[comment.id] %>
</div>
总结:
答案 3 :(得分:2)
好像你需要一个自我指涉的联想。查看以下railscast:http://railscasts.com/episodes/163-self-referential-association
答案 4 :(得分:2)
我们已经这样做了:
我们使用ancestry
gem创建以层次结构为中心的数据集,然后输出部分输出ordered list
:
#app/views/categories/index.html.erb
<% # collection = ancestry object %>
<%= render partial: "category", locals: { collection: collection } %>
#app/views/categories/_category.html.erb
<ol class="categories">
<% collection.arrange.each do |category, sub_item| %>
<li>
<!-- Category -->
<div class="category">
<%= link_to category.title, edit_admin_category_path(category) %>
<%= link_to "+", admin_category_new_path(category), title: "New Categorgy", data: {placement: "bottom"} %>
<% if category.prime? %>
<%= link_to "", admin_category_path(category), title: "Delete", data: {placement: "bottom", confirm: "Really?"}, method: :delete, class: "icon ion-ios7-close-outline" %>
<% end %>
<!-- Page -->
<%= link_to "", new_admin_category_page_path(category), title: "New Page", data: {placement: "bottom"}, class: "icon ion-compose" %>
</div>
<!-- Pages -->
<%= render partial: "pages", locals: { id: category.name } %>
<!-- Children -->
<% if category.has_children? %>
<%= render partial: "category", locals: { collection: category.children } %>
<% end %>
</li>
<% end %>
</ol>
我们还制作了一个嵌套下拉列表:
#app/helpers/application_helper.rb
def nested_dropdown(items)
result = []
items.map do |item, sub_items|
result << [('- ' * item.depth) + item.name, item.id]
result += nested_dropdown(sub_items) unless sub_items.blank?
end
result
end
答案 5 :(得分:2)
这可以通过重新使用或使用特殊的数据结构来解决。递归更容易实现,而像nested_set
gem使用的数据结构更具性能。
首先是一个如何在纯Ruby中工作的例子。
class Comment < Struct.new(:content, :replies);
def print_nested(level = 0)
puts "#{' ' * level}#{content}" # handle current comment
if replies
replies.each do |reply|
# here is the list of all nested replies generated, do not care
# about how deep the subtree is, cause recursion...
reply.print_nested(level + 1)
end
end
end
end
实施例
comments = [ Comment.new(:c_1, [ Comment.new(:c_1a) ]),
Comment.new(:c_2, [ Comment.new(:c_2a),
Comment.new(:c_2b, [ Comment.new(:c_2bi),
Comment.new(:c_2bii) ]),
Comment.new(:c_2c) ]),
Comment.new(:c_3),
Comment.new(:c_4) ]
comments.each(&:print_nested)
# Output
#
# c_1
# c_1a
# c_2
# c_2a
# c_2b
# c_2bi
# c_2bii
# c_2c
# c_3
# c_4
现在通过递归调用Rails查看partials:
# in your comment show view
<%= render :partial => 'nested_comment', :collection => @comment.replies %>
# recursion in a comments/_nested_comment.html.erb partial
<%= nested_comment.content %>
<%= render :partial => 'nested_comment', :collection => nested_comment.replies %>
设置数据库结构,请参阅文档:http://rubydoc.info/gems/nested_set/1.7.1/frames将以下内容(未经测试)添加到您的应用中。
# in model
acts_as_nested_set
# in controller
def index
@comment = Comment.root # `root` is provided by the gem
end
# in helper
module NestedSetHelper
def root_node(node, &block)
content_tag(:li, :id => "node_#{node.id}") do
node_tag(node) +
with_output_buffer(&block)
end
end
def render_tree(hash, options = {}, &block)
if hash.present?
content_tag :ul, options do
hash.each do |node, child|
block.call node, render_tree(child, &block)
end
end
end
end
def node_tag(node)
content_tag(:div, node.content)
end
end
# in index view
<ul>
<%= render 'tree', :root => @comment %>
</ul>
# in _tree view
<%= root_node(root) do %>
<%= render_tree root.descendants.arrange do |node, child| %>
<%= content_tag :li, :id => "node_#{node.id}" do %>
<%= node_tag(node) %>
<%= child %>
<% end %>
<% end %>
<% end %>
此代码来自旧的Rails 3.0应用程序,略有变化且未经测试。因此它可能不会开箱即用,但应该说明这个想法。
答案 6 :(得分:1)
这将是我的方法:
回复有自我参照HABTM
class Reply < ActiveRecord::Base
belongs_to :comment
has_and_belongs_to_many :sub_replies,
class_name: 'Reply',
join_table: :replies_sub_replies,
foreign_key: :reply_id,
association_foreign_key: :sub_reply_id
def all_replies(reply = self,all_replies = [])
sub_replies = reply.sub_replies
all_replies << sub_replies
return if sub_replies.count == 0
sub_replies.each do |sr|
if sr.sub_replies.count > 0
all_replies(sr,all_replies)
end
end
return all_replies
end
end
现在收到评论等的回复:
答案 7 :(得分:0)
我对ActiveRecord可用的不同层次结构宝石有各种不同的体验。通常,您不希望自己这样做,因为您的查询最终效率非常低。
Ancestry的宝石还可以,但我不得不离开它,因为孩子&#39;是范围而不是关联。这意味着您不能使用嵌套属性,因为嵌套属性仅适用于关联,而不适用于范围。根据您正在做的事情,这可能是也可能不是问题,例如通过父级排序或更新兄弟姐妹或在单个操作中更新整个子树/图表。
最有效的ActiveRecord宝石就是Closure Tree宝石,我对它有很好的效果,但由于ActiveRecord的工作方式,整个子树的喷溅/变异都是恶魔般的。如果您在进行更新时不需要在树上计算事物,那么就可以了。
我已经从ActiveRecord转移到Sequel,它具有递归的公用表表达式(RCTE)支持,它由内置的树插件使用。 RCTE树在理论上可以快速更新(只是在一个简单的实现中修改单个parent_id),并且由于它使用的SQL RCTE特性,查询通常也比其他方法快几个数量级。它也是最节省空间的方法,因为只需要parent_id来维护。我不知道任何支持RCTE树的ActiveRecord解决方案,因为ActiveRecord几乎没有覆盖续集所做的SQL频谱。
如果您没有与ActiveRecord结合,那么Sequel和Postgres是一个强大的组合IMO。当您的查询变得如此复杂时,您将发现AR中的缺陷。总是有一种痛苦转移到另一个ORM,因为它不是开箱即用的股票轨道方法,但我已经能够表达我无法用ActiveRecord或ARel做的查询(即使它们非常简单),并且通常与我使用ActiveRecord获得的相比,全面提高了查询性能10-20倍。在我的用例中,维护数据树的速度要快几百倍。这意味着对于相同的负载,我需要的服务器基础设施要少几十到几百倍。想一想。
答案 8 :(得分:-1)
您将在每个回复迭代中收集回复的回复。
<% comment.replies do |reply| %>
<%= reply.content %>
<% reply_replies = Post.where("reply_id = #{reply.id}").all %>
<% reply_replies .each do |p| %>
<%= p.post %>
<% end
<% end %>
虽然我不确定它是否是成本最常规的方式。