I coded up an iterator called traverse, it allows the nested set to be traversed in pre-order (the same as full_set.each), but as well as yielding each node, it yields its level, and the level of the next node relative to it. This makes building hierarchical menus & lists very easy:
>> puts menu = root.traverse("") {|node, level, delta_next| "###" * level << node.name << "\n"}
root
###child1.1
######child2.1
#########child3.1
###child1.2
###child1.3
=> nil
>>
Using the relative level of the next node, a html list can be built:
def display_nested_set(root, li_open = "<li>", li_close = "</li>", l_open = "<ul>", l_close = "</ul>" )
root.traverse("") do |node, level, relative_level_of_next|
str = li_open.dup
str << link_to(node.name, category_path(node))
if relative_level_of_next == 1 # Descending to first child
str << l_open
elsif relative_level_of_next == 0 # Next node is this nodes next sibling
str << li_close
else # Moving back up the tree, relative_level_of_next.abs levels
str << li_close
str << ((l_close << li_close) * relative_level_of_next.abs)
end
str
end
end
Here's the method itself (the set is retrieved in one go with #full_set, there is no more DB access after that):
# traverse {|current_node, level, relative_level_of_next| block }
#
# Iterates through the nodes in the nested set rooted at the receiver, in a pre-order
# traversal. For each node visited, it yields that node, its level, and the relative level
# of the next node to be visited. The values returned by successive block executions are
# accumulated (in an array by default) and returned at the end
#
# The receiver always has a level of 0.
#
# If relative_level_of_next is 1, the next node is gauranteed to be the first (i.e. leftmost) child
# of the current node
# If it is 0, the next node is the next sibling of the the current node
# Otherwise, relative level is a negative integer, indicating how many levels above the current node
# the next node is
def traverse(accum = [])
level = 0
pre_order_array = full_set
(pre_order_array.size - 1).times do |i|
curr_node = pre_order_array[i]
next_node = pre_order_array[i + 1]
distance = next_node.lft - curr_node.lft
relative_level_of_next = -1 * (distance - 2)
accum << yield(curr_node, level, relative_level_of_next)
level += relative_level_of_next
end
# Last node is handled differently to the rest: since there is no next node,
# the relative_level_of_next is the number of levels back up to the root level
relative_level_of_next = - 1 * (pre_order_array.first.rgt - pre_order_array.last.rgt)
accum << yield(pre_order_array.last, level, relative_level_of_next)
end
Please feel free to contact me : briansheehan78<AT>gmail.com