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
:only
or: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
*_path
and*_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
:new
is sufficient for the new thing form, we need to add:create
to have the breadcrumb defined when the validation fails and the form is redisplayed (same for:edit
and:update
, respectively). This may look like a redundancy but that’s how Rails works. Alternatively you can add the breadcrumb from thenew.html.erb
template or inside the:create
action. But I prefer to have them declared in one place if only possible. - If you use
add_breadcrumb
in 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 [/sourcecode] <p>This lets us have breadcrumbs like <em>Home > Categories > Root > Parent > Child</em> for tree-like category hierarchies. The <code>set_breadcrumb_for</code> method traverses all the parents of the category recursively until it finds the root category. I agree that the <code>"category_path(#{cat.id})"</code> part looks a little bit messy, but I couldn't find any cleaner way to do this with the <code>eval</code> solution I mentioned above. Any suggestions welcome.</p> <h1>The implementation</h1> <p>Now for the implementation. It's really quite trivial for the above defined set of the requirements. Just drop these two methods in your <code>ApplicationController</code>: </p> 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 [/sourcecode] <p>We need two versions of <code>add_breadcrumb</code> method. The first one (which is an instance method) is <strong>responsible for constructing the actual breadcrumb list</strong>. It is called several times during request processing. As we can see <code>@breadcrumbs</code> is just a list of two element lists, containing description and a URL.</p> <p>The second version is a class method that we can use in declarations at the top of our controllers. It just <strong>defines an unnamed <code>before_filter</code></strong> which, when called, uses the first version to add breadcrumb element. This way we get all the goodies (<code>:only</code>, <code>:except</code>, ordering) for free.</p> <p>How to display the breadcrumbs? Put something like below to your layout and add HTML tags and styles as needed.</p> <% 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.
June 13th, 2008 at 14:51:24
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(" > ") %>
June 13th, 2008 at 22:18:14
Clean and very elegant, thanks for sharing it!
June 13th, 2008 at 23:11:15
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!
June 14th, 2008 at 03:23:25
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.
June 14th, 2008 at 16:32:31
CrazyDK, thanks for spotting this bug. I updated the
add_breadcrumb
method according to your suggestion. Or you could useroot_path
(it’s probably defined in every Rails app anyway) instead of/
:)June 18th, 2008 at 01:37:01
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)
June 18th, 2008 at 06:48:38
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 :)
June 18th, 2008 at 08:43:58
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.
June 27th, 2008 at 10:40:54
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.
July 9th, 2008 at 09:46:33
And here’s another implementation of the above idea: http://github.com/fesplugas/simplified_breadcrumbs/tree/master.
July 23rd, 2008 at 15:00:22
[…] 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 […]
July 25th, 2008 at 05:39:16
[…] https://szeryf.wordpress.com/2008/06/13/easy-and-flexible-breadcrumbs-for-rails/ […]
July 25th, 2008 at 11:12:07
WELL THANKS I AM A NEW USER I REALLY WANT TO KNOW WHAT
IS ALL ABOUT BECAUSE AM INTERESTED
August 9th, 2008 at 17:03:29
[…] Y vaya que si lo encontre: https://szeryf.wordpress.com/2008/06/13/easy-and-flexible-breadcrumbs-for-rails/ […]
August 31st, 2008 at 07:02:23
I do not believe this
September 19th, 2008 at 12:48:00
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.
September 19th, 2008 at 13:02:12
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.
September 23rd, 2008 at 13:56:48
Thank you… now it is working fine.
November 7th, 2008 at 16:54:27
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.
November 8th, 2008 at 10:07:32
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.
December 25th, 2008 at 11:53:15
Thanks, I just needed this functionality and this post did!
May 6th, 2009 at 21:44:28
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!
June 4th, 2009 at 05:18:33
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?
June 4th, 2009 at 08:56:41
QAS: without looking at your code I can only speculate. You probably call
add_breadcrumb 'Home', '/'
twice. Please check your code for that.July 15th, 2009 at 16:07:25
Thanks! Really useful!
August 8th, 2009 at 09:24:32
this is sweet! thanks a lot!
October 26th, 2009 at 22:00:20
This is by far the cleanest and leanest implementation I have come accross so far (ok, eval is seen by many as “chaeting”, but… who cares, this is a relatively safe way of using it, especially if you make the regex sufficiently strict!). Kudos & thanx!
November 17th, 2009 at 14:06:51
First: Really good work. Thanks! But I have one wish :)
Is there a clean way to remember a whole path?
For Example:
Computer -> CPUs -> IntelCentrino #1
Where ‘Computer’ & ‘CPUs’ are categories and ‘IntelCentrino #1’ is a certain product out of a list.
November 17th, 2009 at 16:08:32
Saba: it depends on what you need to store the breadcrumb for? The breadcrumb is just an array so there are many possibilities.
If you want to store it for caching purposes, you can use
:serialize
in Rails to store it in model table. Or just throw the array at you local memcached.If you want to generate a page of links to various products and those links should look like breadcrumbs, I’m afraid you’ll have to define a method
breadcrumb_for
in your model. But that’s OK, because in this case those link are not breadcrumbs, only look similar :)November 18th, 2009 at 00:10:29
Thanks for the answer szeryf. Actually I just dont want the breadcrumb-navigation to ‘reset’ if you change between different actions (could also be a change of models).
It should be an advancement of the above mentioned dynamical way to recursively build the path for different categories.
I already solved this problem for me by using sessions. Just wanted to know if there is a ‘cleaner’ way.
January 19th, 2010 at 18:53:47
[…] Source: https://szeryf.wordpress.com/2008/06/13/easy-and-flexible-breadcrumbs-for-rails/ […]
April 4th, 2010 at 03:00:56
This approach works pretty well for RESTful resources and all those other gimmicky goodies most people use Rails for, but it kinda falls apart for those one-off informational actions/pages that fall through to the default routes, never touch a database, and genuinely need to specify their titles/crumbs at some point after the controller’s called render.
It also dies in what I believe would be an incredibly common practice: using controller_path as your URL. That method’s lack of a leading slash in its return value leaves you with redundant relative paths in your links, and there will likely be even more situations where simply punting to eval just isn’t going to work that I haven’t encountered yet.
Much like user login management, this is one of those things there’s just no way to produce a generic, drop-in solution that’s flexible enough for everyone.
June 4th, 2012 at 00:02:55
This solution comes absolutely handy for a little project I’m on at the moment. Thank you! Still being a beginner @Rails, I wondered where to put my breadcrumbs method and had just started to type it into the application helper, when I decided to do a quick Google search. Glad I did :-)
July 11th, 2012 at 17:58:07
what does the ||= do for @breadcrumb?
July 11th, 2012 at 18:55:36
The ||= is conditional initialization in Ruby, i.e. it only assigns the value if @breadcrumb is nil.
July 11th, 2012 at 19:55:33
so is @breadcrumb static (doesn’t erase after the first call to add_breadcrumb)?
July 11th, 2012 at 20:14:54
I ask this because, I don’t want to have use use add_breadcrumb multiple times each time a go to a different page under a certain trail. I just want the path to be stores statically and added on to.
July 19th, 2012 at 19:17:09
Hi, thanks for a nice breadcrumb solution. One suggestion: right now, the breadcrumb text is not html escaped, this opens up the risk of XSS. e.g. if @title contains “alert(‘hi’)” add_breadcrumb @title,foo will output the script verbatim instead of the safe “<script…”
The app can do the escaping, but since the breadcrumbs are set on the controller, it is a bit cumbersome:
add_breadcrumb ERB::Util.html_escape(@title), …
I suggest you consider escaping the output as part of the library instead of leaving it to the application.
March 29th, 2017 at 08:26:19
i love it. What if i wanted to make the last element on the trail clickable?
March 29th, 2017 at 09:46:04
Rubyfreak: really easy, just remove the range operation in the view part like this: