#162 Tree Based Navigation
May 18, 2009 | 11 minutes | Active Record, Plugins
The acts_as_tree plugin provides many methods for handling a tree based association. In this episode I show how to build a navigation system with tabs, subpages, and breadcrumbs.
- Download:
- source code
- mp4
- m4v
- webm
- ogv
Nice, episode. I've been using acts_as_tree ever since I started with ROR. One thing I noticed though: What happens if you edit a page so that one of its sub-pages becomes its parent? Wouldn't the whole structure break? Would definitely be great with some validation that takes care of that.
I was going to ask the same question!
You really should look into better_nested_set (http://github.com/chris/better_nested_set/tree/master). Performance-wise its way better then acts_as_tree.
Great one, Ryan! There's one more it could be covered which is showing the hierarchy in a collection_select. tree_select is good but not perfect.
do you have any thoughts on that subject? Thanks! Keep it up!
Hi Ryan,
I use awesome_nested_set, where I can use the order btw. the pages as well.
May be an even deeper screencast would be cool on how to build a sophisticated navigation within a more complex app.
E.g. you are viewing posts in the forum, then even if you are
in /community/forum/topics/5
There are several plugins around for this also, one of them is this:
http://github.com/rpheath/navigation_helper/tree/master
Also, read this post:
http://www.railslodge.com/plugins/1226-awesome-nested-set
Hey Ryan,
great episode. But i have a question. Is it possible to make a dynamic Select Menu with acts_as_tree like you do it in Ep.88?
Awesome episode, Ryan. That's a quick and neat way to generate tree page navigation.
Nice screencast Ryan. If you or anyone is looking for a quick,easy way to generate ascii trees in the console: http://tagaholic.me/2009/03/18/ruby-class-trees-rails-plugin-trees-with-hirb.html#active_record_trees
Great screencast! I´m very interested in how to use the URL in the Page Model for more complex sites with more controllers and actions. Redirect to this URL? I think that´s not a good idea for SEO issues.
Great intro...however, it would be nice to see how to generate the urls with the title of the page.
Typically, for a content-based site, I would rather have /shipping-info instead of /page/1 as the url.
Also, the previous comment about a tree selection menu would be cool to see.
page 1
page 1b
page 1c
page 2
etc.etc..
my company switched what we were using to organize a tree based structure of data to awesome_nested_set, its quicker , easier for doing queries and locating nodes and also has more convenience methods. I highly recommend it (http://github.com/collectiveidea/awesome_nested_set/tree/master). Chef Recommends !
I didn't quite get the part with how to deal with controllers and actions… could you give me some more advices on that? or explain it a bit more in detail or so?
@Fredd, good point. It would be nice if acts_as_tree added these validations for you. Maybe someone has already done this. I haven't looked at the forks.
@José, good question. If you want to nest the elements in the select menu then acts_as_tree is not best, try a nested set instead (see below).
@Ben, @Rafael, @David, thanks for the suggestion on using a nested set. I like the simplicity that acts_as_tree brings, and I think for this case it works well enough because we aren't trying to grab deeply nested child elements in one query.
I plan to do a separate episode on a threaded comment system which uses a nested set.
@Andre, it is definitely possible. You will need to convert the tree into javascript and present it how you want. Sorry I can't go into more detail.
@Jason, it's a secret.
@Geri, @chovy, that is out of the scope of this screencast. But I will consider doing a future episode on the topic. Thanks!
@Nils, instead of a content column in the page model, you would add a "url" string column. You would then link to that URL instead of the page's show action.
<%= link_to h(page.name), page.url %>
To determine the current page I suggest a helper method that looks something like this:
def current_page
Page.find_by_url(request.request_uri)
end
You can then use current_page instead of @page in the view. Of course you may want to make that implementation smarter.
Everything else should be the same.
@Ryan ouch… it is really THAT easy. You just safed me a couple of hours. Thank you very much!
@Ryan, great screencast. Thank you.
@all, I recently made a plugin for creating simple navigations (independent from models). It is well documented and easy to use. Please check it out on github: http://wiki.github.com/andi/simple-navigation
Acts_as_tree has another major downside. When you delete a node it will delete all its decedents with it. This because the has_many children relationship has the :dependent => :destroy option.
Thus deleting a page will also delete all pages below it. Perhaps the alternative act_as_tree plugins make this behavior configurable (as it should).
Hello Ryan, thanks for another great video, but my question is about your textmate. What is that theme used in this screencast?
Great episode. I also covered the acts_as_tree plugin for CMS navigation menus. I also went one step further with being able to sort the menu using the acts_as_list:
http://ramblings.gibberishcode.net/archives/one-activerecord-model-acting-as-a-list-and-tree/33
There's some gotchas to watch out for, esp. with moving nodes around in the tree structure.
@heldopslippers, I don't recommend acts_as_tree for doing this, instead try awesome_nested_set (mentioned in earlier comments).
@daniel, see the About section of this site, the TextMate theme can be found there. :)
@Michael, good point and thanks for the link!
I always seem to get this error:
undefined local variable or method `acts_as_tree' for #<Class:0x2421858>
when I add acts_as_tree to the model. Any idea what might be happening?
I figured it out. I had to restart the server :(
Thanks :)
Its a great idea to review some gems like that for a change Ryan.
Thanks a lot and keep up the good work.
Tree and nested set plugins is a confusing topic. Thanks for clarifying some things Ryan.
acts_as_dag is useful in some circumstances because its fast and it allows for multiple parents, Acts as ordered tree also allows multiple parents but makes a lot of calls to the database. something that tree and nested set plugins are not designed to handle.
Tree and Nested set allow for only a single parent. Nested set plugins should use farey fractions as this gives them a considerable performance boost but most don't. According to the Hyrarchy plugin that uses farey fractions on MySQL, "Hyrarchy scales to at least one million nodes with insertion and access times below 100ms. On SQLite, times are below 200ms."
Thanx a lot Ryan ..
You r a life saver . I was actually looking for a tutorial on acts_as_tree for a long time ..
I am a new in Ror and couldnt have imagine to build anything without your tutorials .
Thanx again dear , U r truly my hero.
MY good wishes r with you always and forever. U really are great and doing a marvellous job
Hello Ryan and thanks for your many and highly appreciated tutorials!
Something I always dread in a Rails project is setting up a good and solid production environment, ie choosing http server, setting up clusters of application servers, setting up the database, etc. Do you think this might be material for an upcomming tutorial maybe? I'm certain more people must share this headache.
BR,
Erik
How can you generate nested URLs?
Ie. page-1 is parent of page-2, page-2 is parent of page-3
and have something like
http://mysite/page1/page2/page3/
Hello there
i need help for adding custom fields .. can u help me out please i need it badly. Ryan please help me on it.
How to associate custom fields with the model.
@Ryan; nice screencast as always.
@José; I converted some work I did on my own tree_select into a plugin. Check it out at; http://github.com/stijnster/tree_select/tree/master
It allows you to display acts_as_tree models as a select-tag (in a nested way). You can do something like this;
<%= f.tree_select :category_id, Category.roots, :id, :name, :include_blank => true %>
ryan, you have been so helpful to me.
Question: I am directing to the URL (not content) but can't figure out how get a sidebar on sub-pages since I have redirected to a new page. Any help would be greatly appreciated.
Many thanks.
I was really excited about learning Ruby and Rails, but it seems EVERY little thing is like banging my head against a wall...
I followed everything and still cannot get this to work. I keep getting "undefined local variable or method `acts_as_tree'"
I installed the plugin per your instructions and script/plugin list includes the acts_as tree plugin
I added acts_as_tree to class Page < ActiveRecord::Base (It's one line how hard can it be!!!)
And I restarted the server (many, many times!!!) I still get undefined local variable or method `acts_as_tree'
If there are additional steps for plugins to be seen I can't find them, and I have googled half a day away!
I finally got it resolved, the plugin installed from the git was "available" in the svn directory but I had to also run script/plugin install acts_as_tree to install it in the apps vendor directory
Could this work with has_and_belongs_to_many? I have a multi-site CMS where each page can belong to multiple sites. The nesting would need to happen in pages_sites.
Fantastic episode.Its a quick and easy way to generate tree page navigation.
Well done, Ryan ! I have a question. How would it be possible to use it with STI? For example, you process some Data objects among which you have Image, Music, Document objects as Data chidren. Every child can have its proper chidren (mp3, CCA for Music, Icons, Photos for Images, etc.). How can we create kind of Data object and precise its type in the moment of creation so that not to write a separate controler for every Data type? Thank you.
Obviously this is an old post, but if anyone is playing with tree based navigation, try this:
http://github.com/ktec/elements
The model uses the "ancestry" to provide a tree structure, and STI to provide different elements of the site. I'm working on getting the wonderful jquery jstree fully functioning, but all the code is there if anyone wants...
Great stuff. I really enjoyed reading this article. I will come back to check it out for update.
It is no mystery for those of us who have been in the design business for a while that a logo should be as simple as it can possibly be–well, many other things should too, but we’ll take it one step at a time.
Total RoR newb here and I have to say this helped me a ton. I ended up moving over to awesome_nested_set for the reasons mentioned in previous comments. But there's a handy converter which makes it painless: Category.rebuild!
Thanks again, your site is awesome!
Thanks for the video.
Has anyone experienced a problem with re-cyclic references?
For example: A has a child B. But later on you update A and change it's parent to B. Then it becomes A has B; B has A...
I wrote a validation to prevent it but although unit test works; it seems it goes into an infinite loop when I do this from frontend. Check here if you want to see the validation I tried.
Yes i'm facing problems with acts_as_tree while referencing another parent.
A has a child B then B becomes the child of C. It doesn't refresh the counter_cache for A and C, and i can't set it manually.
I had to force the update with :
Table.connection.update("UPDATE table SET children_count = #{old_parent['children_count'] -= 1} WHERE id = #{old_parent.id}")
If anyone knows any other solution i'm interested.
If using Rails 3, see: http://rubygems.org/gems/acts_as_tree_rails3