Rails 5:为集合中的每个子对象渲染一个div

Rails 5: Render a div for each child object in a collection

我正在尝试找出一种在页面上使用 reddit 样式嵌套评论的有效方法。我已经按照我想要的方式设置了一切。但是,我无法弄清楚如何以有效的方式呈现评论。请参阅下面我当前的部分内容:

#_comment_list.html.erb
<div class="comment-list">
  <h2>Comments</h2>
  <ul>
    <% @post.comments.each do |c| %>
        <% byebug %>
      <li><%= c.body %></li>
      <% unless c.is_deleted == true %>
        <%= render partial: "shared/comment_form", :locals => { commentable_type: c.class.name, commentable_id: c.id, post: @post.id } if current_user %>
      <% end %>
      <ul>
        <% c.comments.each do |d| %>
          <li><%= d.body %></li>
          <% unless d.is_deleted == true %>
            <%= render partial: "shared/comment_form", :locals => { commentable_type: d.class.name, commentable_id: d.id, post: @post.id } if current_user %>
          <% end %>
        <% end %>
      </ul>
    <% end %>
  </ul>
</div>

显然,这只会呈现一组子评论,如下所示:

Post
  Comment
    Child Comment
    Child Comment
  Comment
  ...

我正在设计一个空白,关于如何根据需要嵌套多次呈现子评论的子评论。

Post
  Comment
    Child Comment
      Grandchild Comment
        Great Grandchild Comment
        Great Grandchild Comment
    Child Comment
  Comment
  ...

如果有人能指出我要去的方向,我将不胜感激。

这里有一些关于我的模型和关联的信息,如果它有助于提出解决方案的话。

# Comment.rb
class Comment < ApplicationRecord
  validates_presence_of :body
  # validates :user_id, presence: true

  belongs_to :user
  belongs_to :commentable, polymorphic: true
  has_many :comments, as: :commentable

  def find_parent_post
    return self.commentable if self.commentable.is_a?(Post)
    self.commentable.find_parent_post # semi recursion will keep calling itself until it .is_a? Post
  end
end

# Post.rb
class Post < ApplicationRecord
  validates :user_id, presence: true
  validates :forum_id, presence: true

  belongs_to :user
  belongs_to :forum

  has_many :comments, as: :commentable
end

create_table "comments", force: :cascade do |t|
  t.text     "body"
  t.datetime "created_at",                       null: false
  t.datetime "updated_at",                       null: false
  t.integer  "commentable_id"
  t.string   "commentable_type"
  t.integer  "user_id"
  t.boolean  "is_deleted",       default: false, null: false
end

create_table "forums", force: :cascade do |t|
  t.string   "name"
  t.text     "description"
  t.integer  "user_id"
  t.datetime "created_at",  null: false
  t.datetime "updated_at",  null: false
  t.index ["user_id"], name: "index_forums_on_user_id", using: :btree
end

create_table "posts", force: :cascade do |t|
  t.string   "title"
  t.text     "description"
  t.integer  "user_id"
  t.integer  "forum_id"
  t.datetime "created_at",  null: false
  t.datetime "updated_at",  null: false
  t.index ["forum_id"], name: "index_posts_on_forum_id", using: :btree
  t.index ["user_id"], name: "index_posts_on_user_id", using: :btree
end

两种方法:

为子评论创建一个引用自身的部分,例如:

# comments/_show.html.erb

<% depth ||= 0 %>
<div class="comment" style="padding-left: <%= 16 * depth %>px;">
  <%= comment.text %>
  <% comment.children.each do |c| %>
    <%= render partial: "comments/show", locals: { comment: c, depth: depth + 1 } %>
  <% end %>
</div>

或者您可以修改 Comment 模型以在数据库级别具有自然嵌套,然后 query/order/indent 它们一次全部嵌套。这个比较复杂,但是非常强大,而且非常好用。

在您的评论中添加 tree_lefttree_rightdepth 列 table(均为正整数)。索引 tree 列。

tree_lefttree_right 是相互唯一的,包含从 1 到(记录数 * 2)的每个数字。这是一个示例树:

test# select id, text, parent, tree_left, tree_right, depth from comments order by tree_left;
+----+----------------------------+-----------+-----------+------------+-------+
| id | text                       | parent    | tree_left | tree_right | depth |
+----+----------------------------+-----------+-----------+------------+-------+
|  1 | Top Level Comment          |      NULL |         1 |         30 |     0 |
|  2 | Second Level               |         1 |         2 |         29 |     1 |
|  3 | Third Level 1              |         2 |         3 |         20 |     2 |
|  5 | Fourth Level 1             |         3 |         4 |          9 |     3 |
| 12 | Fifth Level 4              |         5 |         5 |          6 |     4 |
| 13 | Fifth Level 5              |         5 |         7 |          8 |     4 |
|  6 | Fourth Level 2             |         3 |        10 |         19 |     3 |
|  8 | Fifth Level                |         6 |        11 |         18 |     4 |
|  9 | Sixth Level 1              |         8 |        12 |         13 |     5 |
| 10 | Sixth Level 2              |         8 |        14 |         15 |     5 |
| 11 | Sixth Level 3              |         8 |        16 |         17 |     5 |
|  4 | Third Level 2              |         2 |        21 |         28 |     2 |
|  7 | Fourth Level 3             |         4 |        22 |         27 |     3 |
| 14 | Fifth Level 6              |         7 |        23 |         24 |     4 |
| 15 | Fifth Level 7              |         7 |        25 |         26 |     4 |
+----+----------------------------+-----------+-----------+------------+-------+

使用 depth = 0tree_left = (current largest tree_right + 1)tree_right = (current largest tree_right + 2) 插入顶级评论。

使用 depth = parent.depth + 1tree_left = parent.tree_righttree_right = parent.tree_right + 插入子评论。然后,运行:

UPDATE comments SET tree_left = tree_left + 2 WHERE tree_left >= #{parent.tree_right}
UPDATE comments SET tree_right = tree_right + 2 WHERE tree_right >= #{parent.tree_right}

评论 A 是评论 B 的子评论当且仅当: A.tree_left > B.tree_left,以及 A.tree_right < B.tree_right

所以它的工作方式是您可以通过以下查询获取属于评论 "XYZ" 的树中的所有子项:

Select * from comments where tree_left >= #{XYZ.tree_left} AND tree_right <= #{XYZ.tree_right} ORDER BY tree_left.

要获取评论的所有 PARENTS,请使用相反的符号:

Select * from comments where tree_left <= #{XYZ.tree_left} AND tree_right >= #{XYZ.tree_right} ORDER BY tree_left.

在条件中包含 = 决定是否包含用于生成查询的评论。

tree_left 的顺序很重要,它将它们置于嵌套树顺序中。然后在您的视图中,您可以直接遍历此列表并按深度缩进它们。

有关 tree_left 和 tree_right 为什么有效的更多信息,请查看本文的嵌套集理论部分:http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/

递归就是你要找的。这是一个简单的例子:http://benjit.com/rails/2015/04/01/rendering-partials-with-layouts-recursively/

这里是一些示例代码

#_post.html.erb
<%= render partial: 'comment_list', locals: {commenabel: @post} %>

这是递归部分:

#_comment_list.html.erb
<div>
  <%= commentable.to_s %>
</div>
<ul>
  <% commentable.comments.each do |comment| %>
    <li>
      <%= render partial: 'comment_list', locals: {commentable: comment} %>
    </li>
  <% end %>
</ul>