Nested comments from scratch

后端 未结 9 1240
生来不讨喜
生来不讨喜 2020-12-25 09:03

Let\'s say I have a comment model:

class Comment < ActiveRecord::Base
    has_many :replies, class: \"Comment\", foreign_key: \"reply_id\"
end
         


        
9条回答
  •  情歌与酒
    2020-12-25 09:36

    That can be solved with resursion or with a special data structure. Recursion is simpler to implement, whereas a datastructure like the one used by the nested_set gem is more performant.

    Recursion

    First an example how it works in pure 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
    

    Example

    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
    

    And now with recursive calls of Rails view 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 %>
    

    Nested Set

    Setup your database structure, see the docs: http://rubydoc.info/gems/nested_set/1.7.1/frames That add the something like following (untested) to your app.

    # 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
    
      <%= render 'tree', :root => @comment %>
    # 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 %>

    This code is from an old Rails 3.0 app, slightly change and untested. Therefore it will probably not work out of the box, but should illustrate the idea.

提交回复
热议问题