Did you know? DZone has great portals for Python, Cloud, NoSQL, and HTML5!
DZone Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

Snippets has posted 5886 posts at DZone. View Full User Profile

Acts As Tree Category Display

04.16.2006
Email
Views: 23096
  • submit to reddit
        You should have a categories table with a parent_id field.  Root categories should have the parent_id of 0.

I modified this post:
http://www.bigbold.com/snippets/posts/show/296

because the first one worked if you did stuff in your view to make the root categories come up, but I agree with the commenter that things should be contained to the helper - however I've managed to break the DRY rule in doing so.  If anyone has any suggestions for a better way around this I'd love to hear it, so please comment.  So anyway.. this should list your root categories ONLY and NOT your other sub-cats as well so that you have a very pretty little tree.

Slap this in your view:

<%= display_categories(@categories) %>

And put this in a helper file.  You'll need it in the application helper most likely since you'll want to call it from multiple views.

      def display_categories(categories)
	    	   ret = "<ul>"
	   for category in categories
		   if category.parent_id == 0
			ret += "<li>"
			ret += link_to category.name
			ret += find_all_subcategories(category)
			ret += "</li>"
		   end
		   
	   end
	   ret += "</ul>"
    end
    
   def find_all_subcategories(category)
    if category.children.size > 0
      ret = '<ul>'
      category.children.each { |subcat| 
        if subcat.children.size > 0
          ret += '<li>'
          ret += link_to h(subcat.name), :action => 'edit', :id => subcat
          ret += find_all_subcategories(subcat)
          ret += '</li>'
        else
          ret += '<li>'
          ret += link_to h(subcat.name), :action => 'edit', :id => subcat 
          ret += '</li>'
        end
        }
      ret += '</ul>'
    end
  end
    

Comments

Snippets Manager replied on Wed, 2009/04/29 - 11:33am

A possible refactor of the solutions described above (thanks guys!): def display_tree_recursive(tree, category_id = nil, options = {}, &block) content_tag(:ul, options[:ul_options] || {}) do tree.inject('') do |output, node| output + (if node.category_id == category_id content_tag(:li, options[:li_options] || {}) do yield(node) + (node.children.empty? ? '' : display_tree_recursive(tree, node.id, &block)) end end || '') end end end

Snippets Manager replied on Sat, 2006/08/12 - 1:26pm

My bad, You can get that recursive method working so you can just pass in call to the database. 1 Query with eager loading:
  @tree = Model.find(:all, :include => [ :children ])
Application Helper Method from above:
  def display_tree_recursive(tree, parent_id)
    ret = "\n
    " tree.each do |node| if node.parent_id == parent_id ret += "\n\t
  • " ret += yield node ret += display_tree_recursive(tree, node.id) { |n| yield n } unless node.children.empty? ret += "\t
  • \n" end end ret += "
\n" end
Call from the view
  <%= display_tree_recursive(@tree, nil) { |node| node.name } %>
The second paremter to the display_tree_recursive method is just the parent id you want to start from, my root doesn't have a parent_id so I just use the value nil.

Snippets Manager replied on Sat, 2006/08/12 - 1:26pm

in the above recursive method the line needs to be changed to this to print all leaves. ret += display_tree_recursive(node.children, node.id) { |n| yield n } unless node.children.empty?

Brandon Aaron replied on Wed, 2006/06/07 - 6:35pm

in the above display_tree_recursive using a block the following line: ret += display_tree_recursive(tree, node.id) { |n| yield n } should be something along these lines to avoid invalid xhtml output: ret += display_tree_recursive(tree, node.id) { |n| yield n } unless node.children.size == 0

Snippets Manager replied on Sun, 2006/05/28 - 7:40pm

Oh, and to further expand upon this, use a block to pass how you want each line formatted: def display_tree_recursive(tree, parent_id) ret = "
    " tree.each do |node| if node.parent_id == parent_id ret += "
  • " ret += yield node ret += display_tree_recursive(tree, node.id) { |n| yield n } ret += "
  • " end end ret += "
" end
And call it with a block: <%= display_tree_recursive(@pages, 0) { |n| n.title } %>

Snippets Manager replied on Sun, 2006/05/28 - 7:40pm

If you instead use the following method, you can recurse infinitely into your tree: def display_tree_recursive(tree, parent_id) ret = "
    " tree.each do |node| if node.parent_id == parent_id ret += "
  • " ret += link_to node.title ret += display_tree_recursive(tree, node.id) ret += "
  • " end end ret += "
" end
Just call it in your view with the 0 parent_id: <%= display_tree_recursive(@pages, 0) %>

Snippets Manager replied on Sun, 2006/05/07 - 4:25am

This will cause a N+1 query condition, though, as none of the child nodes have been pulled from the database. To make this a single database call (and speed things up by quite a bit), you need to do something like this: @categories = Category.find( :all, :conditions => 'parent_id IS NULL', :include => [ { :children => [ :children, :parent ] }, :parent ] ) This will get the first two levels of categories. Either that or you select all categories and handle the hierarchy yourself based strictly on id and parent_id values.