Search code examples
ruby-on-railstree

Recursively render tree in Rails


I have a model that acts_as_tree and I want to render it to a nested HTML-list:

<ol>
 <li> Content goes here
  <ol> recursion
    <li>goes</li>
  <ol> here
 </li>
</ol>

This should normally be easy with a recursive function. But Rails forces the strict separation between code and content.

What is the 'Rails way' to recursively render the tree as HTML-List and preserve that separation?


Solution

  • There are two ways to do this:

    • The partial way
    • The recursive function way

    I don't know how acts_as_tree works, but I'll demonstrate how acts_as_nested_set works, which should be similar.

    Since you ask about the recursive way, you can separate content from ruby in a function on the view scope:

    <%
      def draw_tree(node)
        node.children.each do |child|
    %>
          <li><%= child.name %>
    <%
              if child.children.any?
    %>
                  <ol><% draw_tree(child) %></ol>
    <%
              end
    %>
          </li>
    <%
        end; nil
      end
    %>
    

    Then, to render the tree for all your root nodes (I repeat, this is for acts_as_nested_set, you will have to adapt it - that shouldn't be hard to do):

    <ol>
        <% Model.roots.each do |node| %>
            <li>
                <%= node.name %>
                <% if node.children.any? %>
                    <ol><%= draw_tree(node) %></ol>
                <% end %>
            </li>
        <% end %>
    </ol>
    

    All the above code should be in the same template (.html.erb). Of course, you can partialize it.

    The way to do it with partials is similar to the above, but instead of calling a function you render a partial for the node. But I have the impression (which I have not tested thought) that the recursive function should be slightly faster to render than the partial.

    update

    For acts_as_tree you just need:

    root=Model.where(parent_id:nil).first

    So:

    <% root=Model.where(parent_id:nil).first %>
    <ol>
        <% root.children.each do |node| %>
            <li>
                <%= node.name %>
                <% if node.children.any? %>
                    <ol><%= draw_tree(node) %></ol>
                <% end %>
            </li>
        <% end %>
    </ol>