New Rails Navigation plugin
I mentioned before how I was getting the urge to rewrite my navigation_helper plugin, and now I finally have. But really, it takes a totally different approach, so it’s almost Just Another Navigation Plugin (JANP) and not a rewrite. Either way, you can find it on GitHub.
I don’t want to duplicate the README, but I’ll briefly touch on a few things.
Installation
Just to be explicit: navigation plugin on GitHub. Install it like so:
1 | script/plugin install git://github.com/rpheath/navigation.git |
Now onto the usage.
Menu Definition
This plugin requires pre-defined menus before they can be rendered in your views. This is mainly because I like writing straight-up Ruby code more than I like writing ERB code. So, here’s an example menu definition:
1 2 3 4 5 6 | RPH::Navigation::Builder.config do |navigation| navigation.define :authenticated do |menu| menu.item HomeController, :text => 'Welcome' menu.item ContactController, :path => :contact_us_path end end |
The defaults are as you might expect. If no :text or :path options are present, the text would be the controller’s name, titleized, and the path would be the route that matches the controller (for HomeController expect home_path).
Also, you can define as many menus as your application needs using the yielded navigation object. And once you have your menus defined, you can render them via the key used in the definition:
1 | <%= navigation :authenticated %> |
Rule-based Menus and Items
You can pass an :if option to the menu itself and each menu item independently if you have some custom rules that need to be applied before rendering either the menu or a particular item. Here’s how it works:
1 2 3 4 5 6 7 | RPH::Navigation::Builder.config do |navigation| navigation.define :authenticated, :if => Proc.new { |view| view.logged_in? } do |menu| menu.item HomeController, :text => 'Welcome' menu.item ContactController, :path => :contact_us_path menu.item AdminController, :if => Proc.new { |view| view.logged_in_as_admin? } end end |
Notice the :if option after the :authenticated key. Based on the true/false nature of that Proc, that will either render or not render the entire menu. Now, if you look down to the last menu.item, you can see another :if condition used there. That will simply render or not render that specific menu item.
The Proc will be automatically handed the view instance, so any method that ActionView::Base is aware of can be called here. And if you need the controller, just use view.controller.
There are several other things worth checking out, like support for nested menus (just pass a block to the menu.item call), separate action-level menus, and so on. Again, the README has all of the details.
This is the one area in Rails that I don’t like to think much about once it’s done, so I’m trying to come up with a solution that works for every application, despite the number of edge cases. So far, this is probably the closest I’ve come to that realization. I’m currently using it on 2 production applications and things are going just fine so far.
Questions/comments welcome, and as always, feel free to fork and improve!

Jason R. Thursday, 29 Oct, 2009 Posted at 02:07AM
Hi Ryan, after a long while of searching for different menu navigation options I found yours here. Looks like its just what I want too! Very nice. I’m playing around with it now and I’m wondering if there is a way to wrap
<span>tags inside of the<a>tags. Like this:The reason I want the spans is for design purposes. If you want rounded corners on horizontal tabs then I think spans inside the anchors are the best way to go. I new to ruby so I’m struggling with the mod. Any help is greatly appreciated. Thanks!
Ryan Thursday, 29 Oct, 2009 Posted at 09:17AM
Jason R -
If I need to style the link nested in an
<li>tag, I usually style the link itself, starting withdisplay:block(so vertical padding works properly). However, I realize each person has their own process. So, there are a couple of options…1) Setting the
:textoption to include a span tagThat will work without requiring you to modify the code. However, if you definitely want this behavior permanently, you only have to modify one line.
2) Update the code to permanently include a span tag
In lib/navigation/navigator.rb, around line 80, you should see a line like this:
All you have to do is wrap the text with a span tag, so change it to this:
You can literally cut-n-paste if you want. Hope that helps and let me know if you’re still stuck. Also, since both cases happen at initialization, don’t forget to restart your server after making the change.
Jason R Friday, 30 Oct, 2009 Posted at 02:15AM
Thanks for the quick response and help Ryan. I figured out option #2 late last night and it works great. But option #1 might be better for me because I don’t really want to make changes to your plugin since I’d have to redo them each time I get updates for your plugin :). So far it’s working great.
I did end up commenting out the validation that makes sure the controller exists. I wanted to see my navigation on a page even thought I didn’t have all the conrtollers.
I made a modifcation that I recommend be put in your plugin. It allows you the specify the class name for the current menu item. I’ve seen ‘current’ and ‘active’ used.
Ryan Friday, 30 Oct, 2009 Posted at 07:34PM
Jason R -
Cool, glad things are working out. Also, just so you know, you don’t have to pass the controller object to the menu items. You can pass a symbol or a string if you want.
Also, if you want to just check/style tabs before implementing any code/routes, just keep using the same menu item with different text.
Sure, the tabs will all link to the same place, but that might be a better solution than commenting out validations and such. Just an idea.
And yes, I’ve considered making it a per-menu option for the CSS class of the “current” or “active” menu item(s). I have such a strong habit of “current” but that doesn’t mean everyone does :-)
Christoph Pilka Monday, 08 Mar, 2010 Posted at 04:14AM
Hi Ryan,
very cool approach for multi-level navigation! Do you have some hints covering i18n of the nav labels? The menus are defined statically in conf file so I’m not sure how to use Rails i18n for my purpose.
Cheerio, Chris
Ryan Monday, 08 Mar, 2010 Posted at 07:12PM
Christoph Pilka -
You could probably do something like:
Then, in
lib/navigation/navigator.rb(around line 105), you can see how the text is being created. This line in particular:You could probably try changing it to something like this:
Of course this would assume you had your
language.ymlfiles setup to handle the:greeting_textkey. Also, this is untested, but just might be as simple as that.Let me know if you try this and run into any hiccups, I’d be happy to help. Internationalization is probably something I should add support for, anyway.