Recursive Menu
Recursive displaying of a nested set structure can be achieved without recursive code and with a single db access to get data (beware that all_children_count may increase db calls). I use this for a dynamic menu, but of course the same conde can be used with a categories tree too.
Released under MIT license, you can copy and use. Please tell me about any improvement you may discover. (c) Jean-Christophe Michel, Symétrie 2007
Model
class Menu < ActiveRecord::Base
acts_as_nested_set
end
Database
create_table "menus" do |t|
t.column "created_at", :datetime, :null => false
t.column "updated_at", :datetime, :null => false
t.column "parent_id", :integer
t.column "lft", :integer, :null => false
t.column "rgt", :integer, :null => false
t.column "lock_version", :integer, :default => 0
end
Helper
# meuselect is the id of currently selected menu
def showmenu(menuselect)
if menuselect
@menuselect = Menu.find(menuselect.to_i)
selectpath = @menuselect.self_and_ancestors
else
@menuselect = nil
selectpath = []
end
parents = (selectpath.map{|m| m.parent}+[@menuselect]).uniq-[nil]
parents_sql_filter = parents.empty? ? '' : " OR parent_id IN (#{parents.map{|p| p.id}.join(',')})"
# retrieve only the menus that need to be open
allmenus = Menu.find(:all, :conditions => "(parent_id IS NULL #{parents_sql_filter})", :order => 'lft')
# mark where to open and close html lists
@menus = []
allmenus.each_index { |i|
@menus[i] = {:indent => allmenus[i].level,
:title => allmenus[i].title,
:children_count => allmenus[i].all_children_count,
:url => allmenus[i].page ? allmenus[i].page.whole_url : ''}
@menus[i][:selected] = allmenus[i] == @menuselect
}
allmenus[1..-1].each_index { |i|
@menus[i][:open] = @menus[i][:indent] > @menus[i-1][:indent]
@menus[i][:close] = [0, @menus[i][:indent] - @menus[i+1][:indent]].max
}
@menus.first[:open] = true
@menus.last[:open] = false
@menus.first[:close] = 0
@menus.last[:close] = 1
render 'menu/showmenu'
end
View
menu/showmenu.rhtml
<div id="menu">
<% @menus.each do |m|
if m[:open] -%>
<ul><li>
<% else -%>
</li><li>
<% end -%>
<%= link_to m[:title], wiki_url( :url => m[:url], :lang => m[:lang] ), \
{ :class => "menu_" + m[:indent].to_s + (m[:selected]?' menu_select':'') + ((m[:children_count] > 0)?' menu_chapter':' menu_item') } -%>
<% m[:close].times do -%>
</li></ul>
<% end
end -%>
</div>
