#162
May 18, 2009

Tree Based Navigation

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 (19.5 MB, 11:02)
alternative download for iPod & Apple TV (13.3 MB, 11:02)

Episodes

rails navigator
script/plugin install git://github.com/rails/acts_as_tree.git
script/generate nifty_layout
script/generate nifty_scaffold page parent_id:integer name:string content:text
# models/page.rb
acts_as_tree
<!-- pages/_form.html.erb -->
<%= f.collection_select :parent_id, Page.all(:order => "name"), :id, :name, :include_blank => true %>

<!-- layouts/application.html.erb -->
<ul id="menu">
  <% for page in Page.roots %>
    <li><%= link_to h(page.name), page %></li>
  <% end %>
</ul>
<div class="clear"></div>

<!-- pages/show.html.erb -->
<% title @page.name, false %>

<% unless @page.children.empty? %>
<ul id="submenu">
  <% for page in @page.children %>
    <li><%= link_to h(page.name), page %></li>
  <% end %>
</ul>
<% end %>

<p>
<% for page in @page.ancestors.reverse %>
  <%= link_to h(page.name), page %> &gt;
<% end %>
</p>

<h1><%=h @page.name %></h1>

<%= simple_format @page.content %>
#menu {
  list-style: none;
  margin: 0;
  padding: 0;
}

#menu li {
  margin: 0;
  padding: 0;
  float: left;
}

#menu li a {
  display: block;
  padding: 4px 8px;
  text-decoration: none;
  border: solid black 1px;
  color: black;
  background-color: #AEBBE2;
}

#menu li a:hover {
  color: white;
  background-color: #4A63B8;
}

#submenu {
  float: left;
  list-style: none;
  border: solid black 1px;
  padding: 15px 14px;
  margin: 24px 0;
  margin-bottom: 50px;
  margin-right: 20px;
}

RSS Feed for Episode Comments 43 comments

1. Fredd May 18, 2009 at 00:44

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 hole structure break? Would defenently be great with some validation that takes care of that.


2. aurels May 18, 2009 at 01:15

I was going to ask the same question!


3. Ben May 18, 2009 at 01:29

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.


4. José Mota May 18, 2009 at 02:07

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!


5. Rafael May 18, 2009 at 03:21

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


6. Andre May 18, 2009 at 04:43

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?


7. Andy May 18, 2009 at 06:19

Awesome episode, Ryan. That's a quick and neat way to generate tree page navigation.


8. gabriel May 18, 2009 at 06:25

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


9. Geri May 18, 2009 at 07:28

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.


10. Jason Schmidt May 18, 2009 at 07:41

Ryan, how are you reading my mind? Every Monday morning, I come to your site, and see a tutorial about a subject I was just thinking about over the weekend.

Thank you for your hard work. I look forward to seeing the rest of the 'Mastering Rails Form' screencasts as well.


11. chovy May 18, 2009 at 08:27

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..


12. David May 18, 2009 at 08:45

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 !


13. Nils R May 18, 2009 at 10:10

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?


14. Ryan Bates May 18, 2009 at 10:20

@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!


15. Ryan Bates May 18, 2009 at 10:30

@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.


16. Nils R May 19, 2009 at 01:53

@Ryan ouch… it is really THAT easy. You just safed me a couple of hours. Thank you very much!


17. Andi Schacke May 19, 2009 at 04:08

@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


18. Jaime Iniesta May 19, 2009 at 07:19

Thanks once again for your weekly screencast, Ryan :)


19. George May 19, 2009 at 07:47

Thank you Ryan.


20. Bart Teeuwisse May 19, 2009 at 13:55

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).


21. daniel lopes May 19, 2009 at 15:56

Hello Ryan, thanks for another great video, but my question is about your textmate. What is that theme used in this screencast?


22. Michael May 19, 2009 at 19:07

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.


23. Ryan Bates May 20, 2009 at 12:54

@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!


24. sam Hayes May 20, 2009 at 22:33

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?


25. Sam Hayes May 20, 2009 at 22:55

I figured it out. I had to restart the server :(


26. deto May 21, 2009 at 12:27

Its a great idea to review some gems like that for a change Ryan.
Thanks a lot and keep up the good work.


27. Todd Miller May 22, 2009 at 10:16

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."


28. resca May 23, 2009 at 03:41

It's a good video tutorial!


29. Usman Ashraf Khan May 24, 2009 at 04:40

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


30. Erik May 24, 2009 at 11:35

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


31. Arash May 24, 2009 at 23:51

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/


32. Usman May 25, 2009 at 12:16

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.


33. Stijn Mathysen May 27, 2009 at 04:53

@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 %>


34. chris Jun 02, 2009 at 18:03

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.


35. Nastyulya Jun 05, 2009 at 11:44

Thanks.


36. Harry Jun 14, 2009 at 06:18

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!


37. Harry Jun 14, 2009 at 06:57

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


38. Danny Jun 18, 2009 at 13:38

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.


39. eva Sep 22, 2009 at 06:00

Fantastic episode.Its a quick and easy way to generate tree page navigation.


40. bodrum tatil Sep 26, 2009 at 12:39

Thank you Ryan.


41. Javix Oct 14, 2009 at 07:34

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.


42. 电磁铁 Feb 02, 2010 at 23:52

Great article.Thanks.


43. kamagra Mar 19, 2010 at 05:47

good read many thanks


44. m65 field jacket Mar 19, 2010 at 05:47

many thanks

Add your comment:

(SKIP THIS ONE)

(required)

(not shown)


(use pastie or gist for code)

sponsored by:
if you want to help:
required:
Get Quicktime Player
Give Back to Open Source