如何将所有记录从嵌套集合转换为真正的html树
我在我的Rails项目中使用awesome_nested_set
插件。我有两个看起来像这样的模型(简化版):如何将所有记录从嵌套集合转换为真正的html树
class Customer < ActiveRecord::Base
has_many :categories
end
class Category < ActiveRecord::Base
belongs_to :customer
# Columns in the categories table: lft, rgt and parent_id
acts_as_nested_set :scope => :customer_id
validates_presence_of :name
# Further validations...
end
数据库中的树按预期构建。 parent_id
,lft
和rgt
的所有值都是正确的。该树有多个根节点(当然允许在awesome_nested_set
中)。
现在,我想要呈现给定客户的所有类别,如正确排序的树状结构:例如嵌套的<ul>
标签。这不会太困难,但我需要它是有效的(越少的SQL查询越好)。
更新:找出有可能计算没有进一步SQL查询的树中任何给定节点的孩子数:number_of_children = (node.rgt - node.lft - 1)/2
。这并不能解决问题,但它可能被证明是有帮助的。
如果嵌套套件具有更好的功能,那就好了。
因为你已经发现了这个窍门是从平面组建立树:
- 开始与LFT排序的集合中的所有节点的
- 的第一个节点是根将其添加为根树移动到下一个节点
- 如果是前一个节点的孩子(prev.lft和prev.rht之间LFT)添加子树和向前移动一个节点
- 否则移树一个水平和重复测试
见下图:
def tree_from_set(set) #set must be in order
buf = START_TAG(set[0])
stack = []
stack.push set[0]
set[1..-1].each do |node|
if stack.last.lft < node.lft < stack.last.rgt
if node.leaf? #(node.rgt - node.lft == 1)
buf << NODE_TAG(node)
else
buf << START_TAG(node)
stack.push(node)
end
else#
buf << END_TAG
stack.pop
retry
end
end
buf <<END_TAG
end
def START_TAG(node) #for example
"<li><p>#{node.name}</p><ul>"
end
def NODE_TAG(node)
"<li><p>#{node.name}</p></li>"
end
def END_TAG
"</li></ul>"
end
你必须递归渲染一个将自己调用的部分。类似这样的:
# customers/show.html.erb
<p>Name: <%= @customer.name %></p>
<h3>Categories</h3>
<ul>
<%= render :partial => @customer.categories %>
</ul>
# categories/_category.html.erb
<li>
<%= link_to category.name, category %>
<ul>
<%= render :partial => category.children %>
</ul>
</li>
这是Rails 2.3的代码。您必须在此之前调用路线并明确命名部分。
是啊,我想出了相同的解决方案我自己。问题是每次调用'children'都会执行额外的SQL查询(100个子树= 100个SQL查询)。导致经典的N + 1问题。这正是我想要避免的。此外:第一个渲染部分调用应该像' @ customer.categories.roots%>' – 2009-09-03 12:36:30
最近我回答了一个similar question for php(嵌套集==修改的预置树遍历模型)。
基本概念是通过一个SQL查询得到已经订购的节点和深度指示符。从那里它只是通过循环或递归呈现输出的问题,所以应该很容易将其转换为ruby。
我不熟悉awesome_nested_set
插件,但它可能已经包含一个选项来获取深度注释的有序结果,因为在处理嵌套集合时它是一个非常标准的操作/需要。
_tree.html.eb
@set = Category.root.self_and_descendants
<%= render :partial => 'item', :object => @set[0] %>
_item.html。ERB
<% @set.shift %>
<li><%= item.name %>
<% unless item.leaf? %>
<ul>
<%= render :partial => 'item', :collection => @set.select{|i| i.parent_id == item.id} %>
</ul>
<% end %>
</li>
您还可以排序的:
<%= render :partial => 'item', :collection => @set.select{|i| i.parent_id == item.id}.sort_by(&:name) %>
但在这种情况下,你应该删除此行:
<% @set.shift %>
自2009年9月真棒嵌套组包括一个特殊的方法做这个: https://github.com/collectiveidea/awesome_nested_set/commit/9fcaaff3d6b351b11c4b40dc1f3e37f33d0a8cbe
这个方法很多比调用级别更有效,因为它不需要任何额外的数据库查询。
示例:Category.each_with_level(Category.root.self_and_descendants)do | o,level |
我不能去接受的答案,因为旧版本的红宝石是写的,我想。下面是该解决方案为我工作:
def tree_from_set(set)
buf = ''
depth = -1
set.each do |node|
if node.depth > depth
buf << "<ul><li>#{node.title}"
else
buf << "</li></ul>" * (depth - node.depth)
buf << "</li><li>#{node.title}"
end
depth = node.depth
end
buf << "</li></ul>" * (depth + 1)
buf.html_safe
end
它使用可选深度信息简化。 (这种方法的优点是,有没有必要的输入设置为整体结构的叶子),无深度
更复杂的解决方案可以在创业板的GitHub的wiki上找到:
也许有点晚了,但我想分享我的解决方案基于closure_tree
宝石awesome_nested_set
嵌套hash_tree
方法:
def build_hash_tree(tree_scope)
tree = ActiveSupport::OrderedHash.new
id_to_hash = {}
tree_scope.each do |ea|
h = id_to_hash[ea.id] = ActiveSupport::OrderedHash.new
(id_to_hash[ea.parent_id] || tree)[ea] = h
end
tree
end
这将通过lft
下令不是使用助手来解析它的任何范围的工作:
def render_hash_tree(tree)
content_tag :ul do
tree.each_pair do |node, children|
content = node.name
content += render_hash_tree(children) if children.any?
concat content_tag(:li, content.html_safe)
end
end
end
这可以工作。你对awesome_nested_set也是对的。我忍不住想知道为什么这不是首先嵌入到插件中的。谢谢! – 2009-09-05 18:10:43
忘了提及:关于您的解决方案的要点是,它只需要一个SQL查询! – 2009-09-05 18:11:58
http://gist.github.com/460814 – 2010-07-02 01:41:55