Let\'s say I have a comment model:
class Comment < ActiveRecord::Base
has_many :replies, class: \"Comment\", foreign_key: \"reply_id\"
end
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.
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 %>
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.