I like using Bootstrap navs as the primary navigation tool in my Rails projects, but keeping track of the active member of a nav can be tricky. In the past, I’ve experimented with setting tabs based on the current controller, but I always end up coding for various corner cases. My current approach pairs a helper with a simple tab-tracking variable.

I initialize a @tabs hash in my application controller and use it to track key-value pairs where the key represents a nav and the value names a tab within it. In this example, the books controller tells the :application nav to set :books as the active tab. Within the books area, a secondary nav is set to select :all.

# app/controllers/books_controller.rb
class BooksController < ApplicationController
  before_action :set_tabs
  ...
  private
    def set_tabs
      @tabs[:application] = :books
      @tabs[:books] = :all
    end

Rather than add a conditional .active class on every nav item, I wrap a nav_to helper around link_to. It replaces the first argument with a key and a value to check in the @tabs array. It also eliminates some redundancy in setting Bootstrap classes.

# app/helpers/application_helper.rb
module ApplicationHelper
  def nav_to(nav, tab, path, options={})
    options[:class] = 'nav-item nav-link'
    options[:class] << ' active' if @tabs[nav] == tab
    label = options.key?(:label) ? options[:label] : tab.to_s.titleize
    link_to label, path, options
  end
  ...

Usage is pretty simple.

# app/views/layout/application.html.haml
...
.navbar-nav
  = nav_to :application, :books, books_path
  = nav_to :application, :authors, authors_path, label: 'Writers'
...
# app/views/layouts/books.html.haml
...
.navbar-nav
  = nav_to :books, :all, books_path
  = nav_to :books, :staff_picks, staff_picks_path
...

If other people were interested, I might even consider turning this into a gem. It would certainly DRY up my collection of side projects.