8

awesome_nested_set在我的 Rails 项目中使用该插件。我有两个看起来像这样的模型(简化):

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。树有多个根节点(这当然是允许的)。lftrgtawesome_nested_set

现在,我想以正确排序的树状结构呈现给定客户的所有类别:例如嵌套<ul>标签。这不会太难,但我需要它高效(sql 查询越少越好)。

更新:发现可以计算树中任何给定节点的子节点数,而无需进一步的 SQL 查询:number_of_children = (node.rgt - node.lft - 1)/2. 这并不能解决问题,但可能会有所帮助。

4

7 回答 7

7

如果嵌套集具有开箱即用的更好功能,那就太好了,不是吗。

您发现的技巧是从一个平面集构建树:

  • 从一组按 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 
于 2009-09-05T01:27:47.993 回答
5

我最近为 php 回答了一个类似的问题(嵌套集 == 修改的预序树遍历模型)。

基本概念是通过一个 SQL 查询获得已经排序的节点并带有深度指示器。从那里开始,这只是通过循环或递归呈现输出的问题,因此将其转换为 ruby​​ 应该很容易。

我不熟悉awesome_nested_set插件,但它可能已经包含一个选项来获取深度注释的有序结果,因为它是处理嵌套集时非常标准的操作/需要。

于 2009-09-03T18:35:07.740 回答
5

自 2009 年 9 月以来,令人敬畏的嵌套集包括一种特殊的方法来执行此操作: https ://github.com/collectiveidea/awesome_nested_set/commit/9fcaaff3d6b351b11c4b40dc1f3e37f33d0a8cbe

这种方法比调用级别更有效,因为它不需要任何额外的数据库查询。

示例: Category.each_with_level(Category.root.self_and_descendants) 做 |o, level|

于 2011-02-11T15:03:50.583 回答
3

你必须递归地渲染一个会调用自己的部分。像这样的东西:

# 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 代码。您必须在此之前调用路由并明确命名部分。

于 2009-09-03T12:30:25.223 回答
3

_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 %>
于 2010-06-16T10:38:02.247 回答
1

我想,由于它是为旧版本的 ruby​​ 编写的,我无法得到接受的答案。这是对我有用的解决方案:

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

它通过使用可选的深度信息进行了简化。(这种方法的优点是不需要输入集是叶子的整个结构。)

可以在 gem 的 github wiki 上找到更复杂的没有深度的解决方案:

https://github.com/collectiveidea/awesome_nested_set/wiki/How-to-generate-nested-unordered-list-tags-with-one-DB-hit

于 2014-06-15T20:16:13.310 回答
1

也许有点晚了,但我想分享我awesome_nested_set基于closure_treegem 嵌套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
于 2015-11-13T23:09:28.620 回答