Neat Breadcrumbs With Some Ruby Magic

Breadcrumbs are the perfect tool for preventing users to get lost as they go deeper and deeper navigating a web application. They are relatively easy to code and test and you don’t have to worry about the look and feel: Twitter Bootstrap provides. The problem with breadcrums (yes, I found one) is that they require excesive markup, which you can reduce with some Ruby magic.

The following is a 4-level Twitter Bootstrap breadcrumb for a Ruby on Rails website:

<div class="breadcrumb">
  <li>
    <%= link_to "Home", a_root_path %>
    <span class="divider">/</span>
  </li>
  <li>
    <%= link_to "A page", a_page_path %>
    <span class="divider">/</span>
  </li>
  <li>
    <%= link_to "Another page", another_path %>
    <span class="divider">/</span>
  </li>
  <li class="active">
    The final page
  </li>
</div>

The above breadcrumb has a considerable amount of boilerplate code, which can lead to poor maintainability if you have many of them inside your application, which was my case.

From Wikipedia’s definition of boilerplate code:

The need for boilerplate can be reduced through high-level mechanisms such as metaprogramming (which has the computer automatically write the needed boilerplate text), convention over configuration (which provides good default values, reducing the need to specify program details in every project) and model-driven engineering (which uses models and model-to-code generators, eliminating the need for boilerplate manual code).“

In other words, there is always a Ruby way of rewriting boilerplate code:

- breadcrumb do |b|
  b.item "Home", a_root_path
  b.item "A page", a_page_path
  b.item "Another page", another_path
  b.item "The final page"

Source code here at Github.

It works this way:

def breadcrumb
  breadcrumb = BreadcrumbBuilder.new
  yield breadcrumb if block_given?
  content_tag :ul, :class => 'breadcrumb' do
    breadcrumb.collect do |item|
      content_tag :li, :class => item[:class] do
        item[:href] ? content_tag(:a, item[:body], :href => item[:href]) : item[:body]
      end
    end.join(content_tag(:span, "/", :class => 'divider')).html_safe
  end
end
class BreadcrumbBuilder
  include Enumerable

  attr_reader :breadcrumb

  def initialize; @breadcrumb = []; end

  def each(&block)
    breadcrumb.each(&block)
  end

  def item(body, href = nil)
    breadcrumb << { :body => body, :href => href, :class => "#{href ? '' : 'active'}" }
  end
end

The breadcrumb helper instantiates a Ruby Enumerable class called BreadcrumbBuilder and yields it to the block:

breadcrumb = BreadcrumbBuilder.new
yield breadcrumb if block_given?

For each call to the item method of the BreadcrumbBuilder, like the following:

b.item "Home", a_root_path # b is the BreadcrumbBuilder

a new hash with the body, href and class attributes of the link is pushed to the BreadcrumbBuilder:

breadcrumb << { :body => body, :href => href, :class => "#{href ? '' : 'active'}" } # breadcrumb is an array

Note that the each method of the BreadcrumbBuilder forwards the block to breadcrumb array, which contains the previously created links:

def each(&block)
  breadcrumb.each(&block)
end

So when we call collect over the BreadcrumbBuilder, each of the links inside the breadcrumb array will be visited and transformed to HTML anchors through the Rails content_tag helper:

breadcrumb.collect do |item|
  content_tag :li, :class => item[:class] do
    item[:href] ? content_tag(:a, item[:body], :href => item[:href]) : item[:body]
  end
end # a divider is appended

I hope you enjoyed the post, I did!

ruby

Comments

comments powered by Disqus