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.

Était-ce 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:

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

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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top