Easy and flexible breadcrumbs for Rails
Most projects I work on require some implementation of breadcrumbs. Unfortunately most solutions that I found take the very simplistic approach: they split the URL and present each part as a link. I really have a hard time imagining that this can be sufficient for any non-trivial app.
I also found session-based implementation, but according to the above mentioned Wikipedia article classification these are path-style breadcrumbs, while I need location-style with a little bit of attribute-style mixed in where needed. That is, the breadcrumbs should normally reflect the page’s location in some (virtual) website hierarchy. Sometimes, while browsing things that are naturally hierarchical (like forum threads/posts, category trees and so on), the breadcrumb trail should also reflect this hierarchy. Read on to see my take on this problem.
The requirements
First, let’s formulate the requirements for the breadcrumbs solution.
- It should be a list of links with descriptions, except for the last element, which should not be clickable because it represents current page.
- It should have syntax that is easy to use, ideally something like:
add_breadcrumb 'Description', some_path - It should allow us to add arbitrary number of elements, both statically and dynamically.
- The static parts should be similar to filters:
- declared at one place (e.g. at the top of the controller) not littered around actions or views,
- inheritable (so you can declare home link only once, in your
ApplicationController), - with easily recognizable order,
- and support for filter options like
:onlyor:except.
- The dynamic parts can be added at any time during the request processing, from actions, filters of views, if necessary.
Looks like a lot of hard work? Maybe. But my priority was flexibility and ease of use, not necessarily ease of implementation. I want to be able to do things like this:
Simple example
class ApplicationController < ActionController::Base
add_breadcrumb 'Home', '/'
...
end
class ThingsController < ApplicationController
add_breadcrumb 'Things', 'things_path'
add_breadcrumb 'Create a new thing', '', :only => [:new, :create]
add_breadcrumb 'Edit a thing', '', :only => [:edit, :update]
def show
@thing = Thing.find(params[:id])
add_breadcrumb @thing.name, ''
end
...
end
With this setup, we get breadcrumbs like Home > Things for things index, Home > Things > Create a new thing for new thing or Home > Things > some name for show action.
Notes
- We need to quote
*_pathand*_url(like this:'things_path') when outside of actions due to the way these methods are implemented in Rails. I stretched requirement #2 here but I think this is really only a minor inconvenience. When you forget to do this, you’ll get an undefined method error. The quoted string will beeval-ed when the request is processed, which is of course not the cleanest solution, but works for me. - When the added element is guaranteed to be the last, we don’t have to provide the URL for it.
- Although it may seem that
:newis sufficient for the new thing form, we need to add:createto have the breadcrumb defined when the validation fails and the form is redisplayed (same for:editand:update, respectively). This may look like a redundancy but that’s how Rails works. Alternatively you can add the breadcrumb from thenew.html.erbtemplate or inside the:createaction. But I prefer to have them declared in one place if only possible. - If you use
add_breadcrumbin yourApplicationController, you must do it after the methods are defined (it should be under those methods).
Dynamic example
Now for something a little bit more dynamic, let’s assume we have a hierarchy of categories and while browsing a category we want the breadcrumb to show all the ancestor categories:
class CategoriesController < ApplicationController
add_breadcrumb 'Categories', 'categories_path'
def show
@category = Category.find(params[:id])
set_breadcrumb_for @category
end
...
private
def set_breadcrumb_for cat
set_breadcrumb_for cat.parent if cat.parent
add_breadcrumb cat.name, "category_path(#{cat.id})"
end
end
This lets us have breadcrumbs like Home > Categories > Root > Parent > Child for tree-like category hierarchies. The set_breadcrumb_for method traverses all the parents of the category recursively until it finds the root category. I agree that the "category_path(#{cat.id})" part looks a little bit messy, but I couldn’t find any cleaner way to do this with the eval solution I mentioned above. Any suggestions welcome.
The implementation
Now for the implementation. It’s really quite trivial for the above defined set of the requirements. Just drop these two methods in your ApplicationController:
class ApplicationController < ActionController::Base
...
protected
def add_breadcrumb name, url = ''
@breadcrumbs ||= []
url = eval(url) if url =~ /_path|_url|@/
@breadcrumbs << [name, url]
end
def self.add_breadcrumb name, url, options = {}
before_filter options do |controller|
controller.send(:add_breadcrumb, name, url)
end
end
end
We need two versions of add_breadcrumb method. The first one (which is an instance method) is responsible for constructing the actual breadcrumb list. It is called several times during request processing. As we can see @breadcrumbs is just a list of two element lists, containing description and a URL.
The second version is a class method that we can use in declarations at the top of our controllers. It just defines an unnamed before_filter which, when called, uses the first version to add breadcrumb element. This way we get all the goodies (:only, :except, ordering) for free.
How to display the breadcrumbs? Put something like below to your layout and add HTML tags and styles as needed.
<% if @breadcrumbs %>
You are here:
<% @breadcrumbs[0..-2].each do |txt, path| %>
<%= link_to(h(txt), path) %> >
<% end %>
<%= h(@breadcrumbs.last.first) %>
<% end %>
And that’s it. I hope it works for you. Any suggestions and improvements welcome.
UPDATE: I just posted a followup about how to make breadcrumbs work in an internationalized application.
UPDATE #2: you can have problems with using add_breadcrumb in your ApplicationController, as noted by Vinay in the comments below. See notes above how to fix this.
Possibly related posts: (automatically generated)
~ by szeryf on 2008-06-13.
Posted in before_filter, breadcrumb, filters, ruby on rails
Tags: before_filter, breadcrumbs, filters, ruby on rails




I like it. Thanks. I would just suggest maybe a bit cleaner code for rendering:
<%= @breadcrumbs.map { |txt, path| link_to_unless(path.blank?, h(txt), path) }.join(" > ") %>Dejan Simic said this on 2008-06-13 at 14:51:24
Clean and very elegant, thanks for sharing it!
Andrzej said this on 2008-06-13 at 22:18:14
That’s a really good solution, I’m going to update my post to link to this (i have loads of google references to my post…)
Thanks for sharing!
craigtmackenzie said this on 2008-06-13 at 23:11:15
In the implementation of ‘add_breadcrumb’ instance method, the parameter ‘url’ has to be eval-ed when it contains ‘_path’ or ‘_url’ string. Because, if ‘url’ was set to a string started with ‘/’, it is evaluated as RegExp and shows error.
CrazyDK said this on 2008-06-14 at 03:23:25
CrazyDK, thanks for spotting this bug. I updated the
add_breadcrumbmethod according to your suggestion. Or you could useroot_path(it’s probably defined in every Rails app anyway) instead of/:)szeryf said this on 2008-06-14 at 16:32:31
Instead of testing for _url or _path suffixes, you could pass in a symbol, then use the code:
url = eval(url.to_s) if url.is_a? Symbol
that way you know the intent is to eval it instead of hoping the user doesn’t ever want to add a breadcrumb that has _path or _url as part of it’s url (which would be odd, but stranger things have happened)
Dennis Bell said this on 2008-06-18 at 01:37:01
Dennis, good tip, but that would make passing the _path/_url with parameter very awkward (like
add_breadcrumb "...", :"thing_path(@thing)") so I think I’d rather stay with regexp checking of the params.There are so many ways to specify a URL in Rails app that I’m sure there’s always a workaround if something is mistakenly evaled :)
szeryf said this on 2008-06-18 at 06:48:38
This is outstanding, thanks much! I’ve implemented it in the latest site I’m working on, and it’s working like a charm. The dynamic breadcrumb creation is particularly cool and useful.
Chris Bailey said this on 2008-06-18 at 08:43:58
Well it looks that zachinglis went and created a plugin based on (or maybe only inspired by) my findings. I haven’t tried it yet but it looks promising and has more features. Be sure to check it out.
szeryf said this on 2008-06-27 at 10:40:54
And here’s another implementation of the above idea: http://github.com/fesplugas/simplified_breadcrumbs/tree/master.
szeryf said this on 2008-07-09 at 09:46:33
[...] II: the Internationalization This is a quick followup to my breadcrumb solution. If your application is internationalized, I think you might be interested in the code presented [...]
Breadcrumbs II: the Internationalization « require ‘brain’ said this on 2008-07-23 at 15:00:22
[...] http://szeryf.wordpress.com/2008/06/13/easy-and-flexible-breadcrumbs-for-rails/ [...]
FuzzLinks.com » Easy and flexible breadcrumbs for Rails « require ‘brain’ said this on 2008-07-25 at 05:39:16
WELL THANKS I AM A NEW USER I REALLY WANT TO KNOW WHAT
IS ALL ABOUT BECAUSE AM INTERESTED
owens said this on 2008-07-25 at 11:12:07
[...] Y vaya que si lo encontre: http://szeryf.wordpress.com/2008/06/13/easy-and-flexible-breadcrumbs-for-rails/ [...]
A Crazy Agile Web Programmer’s Blog » Rails & Breadcrumbs said this on 2008-08-09 at 17:03:29
I do not believe this
fornetti said this on 2008-08-31 at 07:02:23
Hi,
I am rails beginner. I followed same step you explained above. But
it is giving “HTTP 500 error” and it is working fine when i remove (without home link in breadcrumb)
” add_breadcrumb ‘Home’, ‘/’ ” from application controller. Can i know what is wrong in my case.
vinay said this on 2008-09-19 at 12:48:00
vinay: it’s hard to say why do you get 500 error. Could you take a look into your log file and see if there’s any exception?
I suspect that you use the method add_breadcrumb before you define it. If that’s the case make sure that “add_breadcrumb ‘Home’, ‘/’” line is under the definitions of those methods. I’m going to update the article to mention this.
szeryf said this on 2008-09-19 at 13:02:12
Thank you… now it is working fine.
vinay said this on 2008-09-23 at 13:56:48
To keep your controllers skinny, the breadcrumb ideally is in a helper function. So, although this is a nice solution in many ways, I prefer a breadcrumb function in my application helper. Thanks for the insights though.
Martijn Lafeber said this on 2008-11-07 at 16:54:27
Martijn: I think you take the “skinny controllers” rule too far. In my understanding, the breadcrumb is essentially a function of where you are and where you come from. And that’s what controllers deal with, that’s their responsibility.
Secondly, I don’t see how you could get the whole functionality proposed above using only helper functions.
Another things is that controllers are more easily testable than helpers and this is important to me.
szeryf said this on 2008-11-08 at 10:07:32
Thanks, I just needed this functionality and this post did!
Josh said this on 2008-12-25 at 11:53:15
Thanks for this good stuff! I added the eval business to the name param also just so I could throw the names of individual dynamic elements at the end of the crumb strings works great!
Eric said this on 2009-05-06 at 21:44:28
for some reason i get two home crumbs and then the current crumb.
ex: Home > Home > Model > 2003
Any idea why this could be happening?
QAS said this on 2009-06-04 at 05:18:33
QAS: without looking at your code I can only speculate. You probably call
add_breadcrumb 'Home', '/'twice. Please check your code for that.szeryf said this on 2009-06-04 at 08:56:41