Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deeply Nested Children #12

Closed
ryanb opened this issue Aug 19, 2009 · 8 comments
Closed

Deeply Nested Children #12

ryanb opened this issue Aug 19, 2009 · 8 comments
Labels

Comments

@ryanb
Copy link

ryanb commented Aug 19, 2009

There is a descendants method, however this fetches all descendants into a flat collection. Often times one wants a nested collection of descendants where they can iterate through children and sub-children at each level.

It would be great if there was a method called nested_children or something which would call descendants but then remap the nesting in memory. So calling nested_children on a child would not trigger an SQL call but return the sub-children mapped in memory. One could use recursion to display this. For example:

<ul>
<% for child_node in node.nested_children %>
  <li><%= render child_node %></li>
<% end %>
</ul>

I believe this would also help improve performance on the select helper method since isn't this currently performing a separate SQL query each time the children are fetched?

@retoo
Copy link
Contributor

retoo commented Aug 20, 2009

Take a look at my fork: http://github.com/retoo/awesome_nested_set/tree/master

I've done something similar:
Category.each_with_level(Category.root.self_and_descendants) do |o, level|
puts ((" " * level) + "- #{o}"
end

In this case I wanted the corresponding level of each subelement, but the same system could be used to 'walk' through the tree knowning the current path or recursivly iterate through it. etc.

@bkeepers
Copy link
Contributor

Sounds reasonable. Want to work up a patch with tests?

@retoo
Copy link
Contributor

retoo commented Aug 23, 2009

I've written some tests for each_with_level and pushed them to my repo. FYI, I've rebased my first commit to your current master.

Two things I'm still a bit unsure about.

  1. Where should we place that method?
    a) as it is now, as a class method of the model?
    b) as a instance method? root.each_with_level (which would internally fetch self_and_descendants)
    c) somewhere in the array returned by the different fetcher methods?
    d) the data should be 'embeded' while fetching from the database? So it would be available all the time?
  2. What should the user pass to the method?
    a) the array containing all nodes to be processed?
    b) the root node?

I plan to add other helpers (like the each_with_path) when I get to it. Perhaps some further tests with sub-trees wouldn't hurt.

@lwille
Copy link

lwille commented Mar 4, 2010

Just in case somebody is needing this in-memory building of the tree: Here's the code (also on http://gist.github.com/322213 )

# Mixin for tree generation based on a nested set, just place this in your application_helper.rb
module TreeMethods
  attr_accessor :child_nodes
  attr_accessor :parent_node
  def after_initialize
    @child_nodes = []
    @parent_node = nil
  end
  def recursive_tree nodes=nil, level=0
    if @child_nodes.nil?
      after_initialize
    end
    nodes = self_and_descendants if nodes.nil?
    nodes.shift unless nodes.first.parent_node.nil?
    nodes.each do |node|
      if node.id!=id && node.parent_id == id
        node.extend TreeMethods
        @child_nodes << node
        node.recursive_tree nodes, level+1
      end
    end
    self
  end
end

def nested_tree base
  unless base.nil?
    if base.is_a?(ActiveRecord::Base) && base.respond_to?(:self_and_descendants)
      base.extend TreeMethods;base.recursive_tree 
    elsif base.is_a?(Class) && base.respond_to?(:acts_as_nested_set)
      base.roots.collect{|c| c.extend TreeMethods;c.recursive_tree} unless base.roots.nil?
    end
  end
end  

This can be called like:
nested_tree Category # calling with Class
nested_tree Category.root # calling with Instance
Instance does not have to be a root, but it maybe should have some children ^^

In your view, just call the partial on child_nodes like this:
render :partial=>category.child_nodes
This algorithm might not be as effective as it could - maybe someone can improve on this. I found it useful when generating category trees for my shop project, I also built a method for transforming the resulting tree into a single, deeply-nested JSON object...

@nowhereman
Copy link

Here an helper who is the equivalent of #nested_set_options helper but with only one DB query (http://gist.github.com/551311).

With the help of #each_with_level method and my helper, I think you can close this issue.

@parndt
Copy link
Collaborator

parndt commented Aug 1, 2011

Could someone introduce a pull request which improves performance related to this in the core library? (Is it still an issue?)

@scaryguy
Copy link

@retoo 's solution worked for me. But I think this thing is a must-be as a built-in function.

@stale
Copy link

stale bot commented Jan 11, 2021

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants