Как отобразить все записи из вложенного набора в реальное дерево html
я использую 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
, lft
и rgt
правильные. Дерево имеет несколько корневых узлов (что, конечно, разрешено в awesome_nested_set
).
теперь я хочу отобразить все категории данного клиента в правильно отсортированном дереве, таком как структура: например, вложенный <ul>
метить. Это было бы не слишком сложно, но мне нужно, чтобы это было эффективно (чем меньше sql-запросов, тем лучше).
обновление: выяснил, что можно вычислить количество детей для любого заданного узла в дереве без дальнейших SQL-запросов:number_of_children = (node.rgt - node.lft - 1)/2
. Это не решает проблему, но может оказаться полезным.
7 ответов
было бы неплохо, если бы вложенные наборы имели лучшие функции из коробки, не так ли?
трюк, как вы обнаружили, состоит в том, чтобы построить дерево из плоского набора:
- начните с набора всех узлов, отсортированных по lft
- первый узел является корнем добавить его в качестве корня дерева перейти к следующему узлу
- если это дочерний узел предыдущего узла (lft между prev.lft и prev.rht) добавьте ребенка в дерево и переместите его вперед узел
- в противном случае переместите дерево на один уровень и повторите тест
см. ниже:
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
Я ответил аналогичный вопрос для php недавно (вложенный набор = = измененная модель обхода дерева предзаказа).
основная концепция состоит в том, чтобы получить узлы уже упорядоченные и с индикатором глубины с помощью один SQL-запрос. Оттуда это просто вопрос рендеринга вывода через цикл или рекурсию, поэтому его должно быть легко преобразовать в ruby.
Я не знаком с awesome_nested_set
plug in, но он уже может содержать опцию получите аннотированный, упорядоченный результат глубины, так как это довольно стандартная операция/необходимость при работе с вложенными наборами.
с сентября 2009 года awesome вложенный набор включает в себя специальный метод для этого: https://github.com/collectiveidea/awesome_nested_set/commit/9fcaaff3d6b351b11c4b40dc1f3e37f33d0a8cbe
этот метод намного эффективнее, чем вызов уровня, потому что он не требует дополнительных запросов к базе данных.
Пример: Категория.each_with_level(категория.корень.self_and_descendants) do|o, level/
вы должны рекурсивно отобразить частичное, которое будет вызывать себя. Что-то вроде этого:--2-->
# 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. Вы должны будете назвать маршруты и назвать частичное явно до этого.
_tree.формат html.eb
@set = Category.root.self_and_descendants
<%= render :partial => 'item', :object => @set[0] %>
_item.формат html.Эрб
<% @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 %>
Я не мог получить принятый ответ из-за старой версии 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
это упрощается с помощью дополнительно подробную информацию. (Преимущество такого подхода заключается в том, что нет необходимости для ввода всей структуре листьев.)
более сложное решение без глубины можно найти на GitHub wiki джем:
может быть, немного поздно, но я хотел бы поделиться мое решение для awesome_nested_set
на основе closure_tree
gem вложенные 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
чем использовать helper для его визуализации:
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