如何将嵌套集中的所有记录呈现为真正的html树
我在我的Rails项目中使用了awesome_nested_set
插件。 我有两个看起来像这样的模型(简化):
class Customer < ActiveRecord::Base has_many :categories end class Category :customer_id validates_presence_of :name # Further validations... end
数据库中的树按预期构造。 parent_id
, lft
和rgt
所有值都是正确的。 树有多个根节点(当然在awesome_nested_set
允许)。
现在,我想在一个正确排序的树中呈现给定客户的所有类别,例如结构:例如嵌套的
- 标签。 这不会太难,但我需要它才能有效(sql查询越少越好)。
- 以一组按lft排序的所有节点开始
- 第一个节点是根添加它,因为树的根移动到下一个节点
- 如果它是前一个节点的子节点(prev.lft和prev.rht之间的lft)将一个子节点添加到树中并向前移动一个节点
- 否则将树向上移动一级并重复测试
#{node.name}
" end def END_TAG "- <%= link_to category.name, category %>
- <%= render :partial => category.children %>
- <%= item.name %> <% unless item.leaf? %>
- <%= render :partial => 'item', :collection => @set.select{|i| i.parent_id == item.id} %>
- #{node.title}" else buf << "
- #{node.title}" end depth = node.depth end buf << " " * (depth + 1) buf.html_safe end
更新:计算出可以在没有进一步SQL查询的情况下计算树中任何给定节点的子节点数: number_of_children = (node.rgt - node.lft - 1)/2
。 这并不能解决问题,但可能会有所帮助。
如果嵌套集具有开箱即用的更好的function将是不错的。
您发现的技巧是从平面集构建树:
见下文:
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 <#{node.name}
" end def NODE_TAG(node) "
" end
我最近回答了类似的php问题 (嵌套set == modified preorder tree traversal model)。
基本概念是通过一个SQL查询获取已经排序的节点和深度指示器。 从那里它只是通过循环或递归渲染输出的问题,所以应该很容易将其转换为ruby。
我不熟悉awesome_nested_set
插件,但它可能已经包含一个获取深度注释,有序结果的选项,因为在处理嵌套集时它是一个非常标准的操作/需要。
自2009年9月以来,令人敬畏的嵌套集包括一个特殊的方法来执行此操作: https : //github.com/collectiveidea/awesome_nested_set/commit/9fcaaff3d6b351b11c4b40dc1f3e37f33d0a8cbe
此方法比调用级别更有效,因为它不需要任何其他数据库查询。
示例:Category.each_with_level(Category.root.self_and_descendants)do | o,level |
您必须以递归方式呈现将调用自身的部分。 像这样的东西:
# customers/show.html.erb Name: <%= @customer.name %>
Categories
<%= render :partial => @customer.categories %>
# categories/_category.html.erb
这是Rails 2.3代码。 在此之前,您必须调用路由并明确命名部分。
_tree.html.eb
@set = Category.root.self_and_descendants <%= render :partial => 'item', :object => @set[0] %>
_item.html.erb
<% @set.shift %>
你也可以对他们进行排序:
<%= render :partial => 'item', :collection => @set.select{|i| i.parent_id == item.id}.sort_by(&:name) %>
但在这种情况下你应该删除这一行:
<% @set.shift %>
我想,因为我写的旧版ruby,我无法完成接受的答案。 这是适合我的解决方案:
def tree_from_set(set) buf = '' depth = -1 set.each do |node| if node.depth > depth buf << "
" * (depth - node.depth) buf << "
通过使用可选的深度信息简化了它。 (这种方法的优点是输入集不需要是叶子的整个结构。)
没有深度的更复杂的解决方案可以在gem的github wiki上找到:
也许有点晚了但我想基于closure_tree
gem嵌套的hash_tree
方法分享我对awesome_nested_set
的解决方案:
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