Update: Check out https://www.driftingruby.com/episodes/benchmarking-and-refactoring-the-content_for-view-helper for a screencast update to this article where things are refactored to reduce technical debt.

So, this isn’t probably even the best way to do this, but I was playing around in a side project and wanted to see what I could do to have some consistency throughout my application; specifically around the breadcrumbs.

So on this project, I have breadcrumbs whose HTML looks something like this. There’s nothing fancy going on here, just some simple HTML. However, this would not be pretty, nor DRY, to repeat this throughout every single page of my project.

<nav class="breadcrumb"> <a class="breadcrumb-item" href="/"><i class="fa fa-home"></i> Home</a> <span class="breadcrumb-item ">Users</span> <span class="breadcrumb-item active">Edit</span> </nav> 1 2 3 4 5 6 7 < nav class = "breadcrumb" > < a class = "breadcrumb-item" href = "/" > < i class = "fa fa-home" > < / i > Home < / a > < span class = "breadcrumb-item " > Users < / span > < span class = "breadcrumb-item active" > Edit < / span > < / nav >

This would ultimately look something like this where links are supported, and items are divided by a slash. If the item was the last one in the list, then it should have an active class.

So, naturally, I created a partial in the layouts folder called _breadcrumbs.html.erb and placed the code in there. However, I still had the issue where the breadcrumbs had to be dynamically updated on each page. Since this information is available at render time, I figured that I could somehow pass the navigation hierarchy from the rendered view to this partial. Ideally, I would have something within each view where I could customize the items in the list and then pass this into the partial.

The solution I came up with and was okay with having within each view was this line of code at the top.

<%= content_for :breadcrumbs, ['Users', 'Edit'] %> 1 2 3 <%= content_for : breadcrumbs , [ 'Users' , 'Edit' ] %>

Now that I had some sort of hierarchy items listed, I needed to be able to parse and pull this information into my breadcrumbs partial.

<nav class="breadcrumb"> <%= link_to icon('home', 'Home'), root_path, class: 'breadcrumb-item' %> <%= yield(:breadcrumbs) if content_for?(:breadcrumbs) %> </nav> 1 2 3 4 5 6 < nav class = "breadcrumb" > <%= link _ to icon ( 'home' , 'Home' ) , root_path , class : 'breadcrumb-item' %> <%= yield ( : breadcrumbs ) if content_for ? ( : breadcrumbs ) %> < / nav >

Making progress!

However, it turns out that this is not an array even though it looks like one. Let’s try to parse it out!

<nav class="breadcrumb"> <%= link_to icon('home', 'Home'), root_path, class: 'breadcrumb-item' %> <% breadcrumbs = JSON.parse(sanitize yield(:breadcrumbs)) if content_for?(:breadcrumbs) %> <%= breadcrumbs %> <%= breadcrumbs.class %> </nav> 1 2 3 4 5 6 7 8 < nav class = "breadcrumb" > <%= link _ to icon ( 'home' , 'Home' ) , root_path , class : 'breadcrumb-item' %> <% breadcrumbs = JSON . parse ( sanitize yield ( : breadcrumbs ) ) if content_for ? ( : breadcrumbs ) %> <%= breadcrumbs %> <%= breadcrumbs . class %> < / nav >

Making more progress! Now, our :breadcrumbs is an Array.

So, now we can loop through each one of these in the breadcrumbs partial!

<nav class="breadcrumb"> <%= link_to icon('home', 'Home'), root_path, class: 'breadcrumb-item' %> <% breadcrumbs = JSON.parse(sanitize yield(:breadcrumbs)) if content_for?(:breadcrumbs) %> <% breadcrumbs.each do |breadcrumb| %> <%= content_tag :span, breadcrumb, class: "breadcrumb-item" %> <% end if content_for?(:breadcrumbs) %> </nav> 1 2 3 4 5 6 7 8 9 < nav class = "breadcrumb" > <%= link _ to icon ( 'home' , 'Home' ) , root_path , class : 'breadcrumb-item' %> <% breadcrumbs = JSON . parse ( sanitize yield ( : breadcrumbs ) ) if content_for ? ( : breadcrumbs ) %> <% breadcrumbs . each do | breadcrumb | %> <%= content_tag : span , breadcrumb , class : "breadcrumb-item" %> <% end if content_for ? ( : breadcrumbs ) %> < / nav >

So with looping through each one of these, we’re almost there! Incase there was no content_for within the view, I wanted to make sure the application did not act funny so I ended the loop with a conditional check.

We are almost there! Now, we need to make the last item in the list a different color by adding the active class to the span element.

<nav class="breadcrumb"> <%= link_to icon('home', 'Home'), root_path, class: 'breadcrumb-item' %> <% breadcrumbs = JSON.parse(sanitize yield(:breadcrumbs)) if content_for?(:breadcrumbs) %> <% breadcrumbs.each do |breadcrumb| %> <%= content_tag :span, breadcrumb, class: "breadcrumb-item #{breadcrumb.equal?(breadcrumbs.last) ? 'active' : nil}" %> <% end if content_for?(:breadcrumbs) %> </nav> 1 2 3 4 5 6 7 8 9 < nav class = "breadcrumb" > <%= link _ to icon ( 'home' , 'Home' ) , root_path , class : 'breadcrumb-item' %> <% breadcrumbs = JSON . parse ( sanitize yield ( : breadcrumbs ) ) if content_for ? ( : breadcrumbs ) %> <% breadcrumbs . each do | breadcrumb | %> <%= content_tag : span , breadcrumb , class : "breadcrumb-item #{breadcrumb.equal?(breadcrumbs.last) ? 'active' : nil}" %> <% end if content_for ? ( : breadcrumbs ) %> < / nav >

There we go!

While we could go ahead and call it quits, I wanted to take this a step further. Sometimes, it is extremely handy to have clickable breadcrumbs that can take a user to a different page. Not all breadcrumb items might have a relating page to display, so I needed to expand this so that we could dynamically display static text or some sort of link to a different page.

So the idea was to have my normal array of items passed into the :breadcrumbs , but also provide an array for an item where the first element is the display text and the second is the path.

<%= content_for :breadcrumbs, ['Static 1',['Dynamic 1', root_path], ['Dynamic 2', 'https://google.com'], 'Static 2'] %> 1 2 3 <%= content_for : breadcrumbs , [ 'Static 1' , [ 'Dynamic 1' , root_path ] , [ 'Dynamic 2' , 'https://google.com' ] , 'Static 2' ] %>

Within the breadcrumbs partial, I created a case logic where we test the class of the item to see if it is a string or array. If the breadcrumb item is an array, then we break it out to string & link and then display it . Otherwise, if the item is a string, then we keep the normal action of displaying the span tag.

<nav class="breadcrumb"> <%= link_to icon('home', 'Home'), root_path, class: 'breadcrumb-item' %> <% breadcrumbs = JSON.parse(sanitize yield(:breadcrumbs)) if content_for?(:breadcrumbs) %> <% breadcrumbs.each do |breadcrumb| %> <% case breadcrumb.class.to_s %> <% when 'Array' %> <% string, link = breadcrumb %> <%= link_to string, link, class: "breadcrumb-item #{breadcrumb.equal?(breadcrumbs.last) ? 'active' : nil}" %> <% when 'String' %> <%= content_tag :span, breadcrumb, class: "breadcrumb-item #{breadcrumb.equal?(breadcrumbs.last) ? 'active' : nil}" %> <% end %> <% end if content_for?(:breadcrumbs) %> </nav> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 < nav class = "breadcrumb" > <%= link _ to icon ( 'home' , 'Home' ) , root_path , class : 'breadcrumb-item' %> <% breadcrumbs = JSON . parse ( sanitize yield ( : breadcrumbs ) ) if content_for ? ( : breadcrumbs ) %> <% breadcrumbs . each do | breadcrumb | %> <% case breadcrumb . class . to_s %> <% when 'Array' %> <% string , link = breadcrumb %> <%= link _ to string , link , class : "breadcrumb-item #{breadcrumb.equal?(breadcrumbs.last) ? 'active' : nil}" %> <% when 'String' %> <%= content_tag : span , breadcrumb , class : "breadcrumb-item #{breadcrumb.equal?(breadcrumbs.last) ? 'active' : nil}" %> <% end %> <% end if content_for ? ( : breadcrumbs ) %> < / nav >

Things are looking great!

So, now on any page that I want to display a hierarchy of breadcrumbs item, I will simple make a content_for :breadcrumbs with an array of items.

<%= content_for :breadcrumbs, [['Users', users_path],'Edit'] %> 1 2 3 <%= content_for : breadcrumbs , [ [ 'Users' , users_path ] , 'Edit' ] %>

We could change this around a bit if we needed other stuff like icons in the breadcrumbs. If I did have to do this approach, an additional element to the breadcrumb item array could be added. However, at this point the array starts to get too complicated and hard to read. Instead, I would create a hash and then reference the items via their key. This could even be extracted to a helper method in app/helpers , but I was happy enough with this implementation for now since it did the job and works well. My main concern was having a fairly unobtrusive way to customize the breadcrumb items within the view.