Comment rendre tous les enregistrements d'un ensemble imbriqué dans un arbre réel html
-
21-09-2019 - |
Question
J'utilise le plugin awesome_nested_set
dans mon projet Rails. J'ai deux modèles qui ressemblent à ceci (simplifié):
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
L'arbre dans la base de données est construite comme prévu. Toutes les valeurs de parent_id
, lft
et rgt
sont corrects. L'arbre comporte plusieurs noeuds racine (ce qui est bien sûr admis dans awesome_nested_set
).
Maintenant, je veux rendre toutes les catégories d'un client donné dans un arbre correctement triés comme la structure: par exemple imbriqués balises <ul>
. Ce ne serait pas trop difficile mais je besoin d'être efficace (moins de requêtes SQL mieux).
Mise à jour: Figured qu'il est possible de calculer le nombre d'enfants pour un nœud donné dans l'arborescence sans autre requêtes SQL: number_of_children = (node.rgt - node.lft - 1)/2
. Cela ne résout pas le problème, mais il peut se révéler utile.
La solution
Ce serait bien si les ensembles imbriqués avaient de meilleures caractéristiques de la boîte ne serait-il.
L'astuce que vous avez découvert est de construire l'arbre d'un ensemble plat:
- commencer par un ensemble de tous les nœuds triés par LFT
- le premier noeud est une racine ajoutez comme la racine du mouvement de l'arbre à nœud suivant
- si elle est un enfant du nœud précédent (LFT entre prev.lft et prev.rht) ajouter un enfant à l'arbre et aller de l'avant un noeud
- sinon déplacer l'arbre d'un niveau et répéter test
voir ci-dessous:
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
Autres conseils
Je répondu à une question similaire pour php récemment (ensemble imbriqué == modifié précommande modèle traversal arbre).
Le concept de base est d'obtenir les noeuds déjà commandés et avec un indicateur de profondeur au moyen de une requête SQL . De là, il est juste une question de rendre la sortie par boucle ou récursion, il devrait donc être facile à convertir en rubis.
Je ne suis pas familier avec le awesome_nested_set
brancher, mais il peut déjà contenir une option pour obtenir la profondeur annotées, résultat commandé, car il est une opération assez standard / besoin en cas d'ensembles imbriqués.
Depuis septembre 2009 ensemble imbriqué impressionnant comprend une méthode spéciale pour ce faire: https://github.com/collectiveidea/awesome_nested_set/commit/9fcaaff3d6b351b11c4b40dc1f3e37f33d0a8cbe
Cette méthode est beaucoup plus efficent que d'appeler le niveau, car il ne nécessite pas de requêtes de base de données supplémentaires.
Exemple: Category.each_with_level (Category.root.self_and_descendants) do | o, niveau |
Vous devez rendre une récursive partielle qui s'appeler. Quelque chose comme ceci:
# 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>
est le code Rails 2.3. Vous devez appeler les routes et le nom de la partie avant que explicitement.
_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>
Vous pouvez également trier leur:
<%= render :partial => 'item', :collection => @set.select{|i| i.parent_id == item.id}.sort_by(&:name) %>
mais dans ce cas, vous devez supprimer cette ligne:
<% @set.shift %>
Je ne pouvais pas travailler la réponse acceptée en raison de l'ancienne version de rubis, il a été écrit pour, je suppose. Voici la solution de travail pour moi:
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
Il est simplifié en utilisant les option informations de profondeur. (L'avantage de cette approche est qu'il n'y a pas besoin de l'ensemble d'entrée pour être toute la structure des feuilles.)
solution plus complexe sans profondeur se trouve sur le wiki github de la gemme:
Peut-être un peu en retard mais je voudrais partager ma solution pour awesome_nested_set
basée sur gem closure_tree
nichée hash_tree
méthode :
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
Cela fonctionne avec une portée ordonnée par lft
Que helper utiliser pour le rendre:
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