A common feature of many web sites is what’s known as a breadcrumb trail, or sometimes a rabbit trail. A breadcrumb trail is just a listing, generally on one concise line, of the pages that have been traversed in a hierarchy to get to the current page. Each element of the breadcrumb trail is a link to that previous page, and there’s usually some character or image separating the elements of a rabbit trail.
Antimatter < Tools < Home
I decided to create some code in Ruby that would facilitate the creation of breadcrumb trails for Rails web sites. Listing 1 shows the code that I came up with. My goal, of course, was to make this a generic solution that could easily be reused in numerous web sites.
In this article, I’ll just focus on presenting the code that generates the breadcrumbs. In a follow-up article, I’ll detail some strategies for packaging up this type of feature to facilitate code reuse.
Listing 1: Helper Code for Breadcrumbs – breadcrumbs_trail.rb
module BreadcrumbsTrail
# Defines the separator used between breadcrumb elements when
# the breadcrumbs are traversed from right to left, i.e. - the
# separator points to left.
def breadcrumb_separator_left
"<"
end
# Defines the separator used between breadcrumb elements when the
# breadcrumbs are traversed from left to right, i.e. - the separator
# points to the right. This is the direction in which most
# breadcrumbs are oriented.
def breadcrumb_separator_right
">"
end
# Returns TRUE if the provided value is an external URL and FALSE if
# it represents the name of a controller. External URL's can be easily
# distinguished because they begin with "http://".
def is_external_breadcrumb(val)
val.start_with?('http://')
end
# Returns a string containing the HTML for one breadcrumb link within
# a breadcrumb trail. The first argument is the title of the link, while
# the second is an array containing the components necessary to build
# the destination URL for the link.
#
# WARNING: Does not work in controllers because the link_to method
# is only available to views.
def build_crumb(title, args)
str = ""
if is_external_breadcrumb(args[0])
str += "<a href='#{args[0]}'>#{title}</a>"
else
cmd = "link_to '#{title}', :controller => '#{args[0]}'"
cmd += ", :action => '#{args[1]}'" unless args.size < 2
str += "#{eval cmd}"
end
str
end
# Returns a string containing the HTML necessary to display a breadcrumb
# trail. The first arg contains an array of elements, where the first
# element is the name of a breadcrumb, the second is an array containing
# values for assembling a URL (controller, controller & action, or
# external URL), and so on in alternating fashion. The final arg is a
# hash containing options, where the only option currently defined
# is ":direction". This can have values of either "left" or "right", and
# governs which way the breadcrumbs will be oriented. The default
# is "right".
#
# An example of the method's usage in a view is:
#
# <%= show_breadcrumbs(
# ['Home', ['main'],
# 'Tools', ['tools'],
# 'Antimatter', ['tools', 'antimatter']],
# :direction => 'left') %>
def show_breadcrumbs(crumbs, opts = nil)
direction = 'right' # Default direction
separator = breadcrumb_separator_right # Default separator
if opts != nil
dir = opts[:direction]
if dir == 'left'
direction = dir
separator = breadcrumb_separator_left
end
end
str = ""
if crumbs.size > 0
str += '<div id="breadcrumbs">'
if direction == 'right'
i = 0
while i < crumbs.size
args = crumbs[i + 1]
str += " #{separator} " if i > 0
str += build_crumb(crumbs[i], args)
i += 2
end
else # Direction equals left
i = crumbs.size - 2
while i >= 0
args = crumbs[i + 1]
str += " #{separator} " if i < (crumbs.size - 2)
str += build_crumb(crumbs[i], args)
i -= 2
end
end
str += '</div>'
end
str
end
end
In Listing 1, the show_breadcrumbs method is the real workhorse, i.e. – the method that users are expected to use to generate their breadcrumbs trail. The code can be included within a helper, such as application_helper.rb. From there, the code would be accessible to all views within a Rails application.
Within a view, the show_breadcrumbs method could be called to generate the HTML for the breadcrumb trail:
<%= show_breadcrumbs(
['Home', ['main'],
'Tools', ['tools'],
'Antimatter', ['tools', 'antimatter']],
:direction => 'left') %>
The first argument is an array, where each breadcrumb element is represented by a pair of array entries. The first value is the name of the breadcrumb, e.g. – the name of the link. The second is an array containing the data necessary to construct the link, such as [controller, action] or [url].
After the initial array argument, the method accepts options. The only currently supported option is “:direction” which can have values of “left” or “right” to define the direction of flow for the trail.
Paths Not Chosen
Is a breadcrumb trail an element of a view? Or should the generation of a breadcrumb trail be the responsibility of a controller?
There’s no right answer, but I chose to consider it part of a view. I placed the breadcrumb generation code in application_helper.rb, which makes it available to all of the views in a Rails application.
include BreadcrumbTrail
The code could just as reasonably be included in a controller. Of course, it wouldn’t work as-is because the build_crumb method won’t work in a controller. It uses the link_to helper method, which is only available in views. Still, if the method were rewritten to be independent, it could be used in a controller.
The obvious location to reference the library would be application_controller.rb, which would make the code available to all other controllers.
A controller could generate the HTML for a breadcrumb trail as shown below:
@trail = show_breadcrumbs(
['Home', ['main'],
'Tools', ['tools'],
'Antimatter', ['tools', 'antimatter']],
:direction => 'left')
The @trail variable will be visible to the associated view. The breadcrumb trail could be included within a view as shown below:
<%= @trail %>
Another design decision that I made was to use an array to represent the elements of the breadcrumb trail, with each element represented by two entries within the array. At first glance, it would seem more logical to use a hash. After all, it would be easy to set up a hash with entries for “Home,” “Tools,” and “Antimatter.”
The value for each hash entry would then be an array. The downside is that a hash doesn’t guarantee the order in which the keys are stored, which makes it problematic to retrieve the breadcrumb entries in the proper order. So, the hash is out as a solution.
Note: Hash order is not guaranteed in Ruby 1.8.x. However, in Ruby 1.9 the hash order will be preserved, which will potentially make hashes a lot more useful.
Conclusion
We’ve ended up with a relatively simple set of methods for generating breadcrumbs. It probably doesn’t handle all cases, but it handles most of the likely ones. As a fall-back, users can explicitly provide a URL for the link, which should allow users to handle cases where controller and action are not sufficient to build a full URL for the link.
It’s not an ideal solution, though. A high proportion of users are likely to want to generate a trail in a controller, which this code doesn’t support. Plus, the array syntax for defining a breadcrumb trail still seems a little cumbersome, though still workable.
In Part 2 of this article, we’ll look at some ways to futher improve this solution.