Using filters to easily create DSLs

Contents

It’s common to see something like this in a Rails controller:

class PostsController < ApplicationController 
  def index 
    @bg_class = "bg-dark"
  end
  
  def show 
    @bg_class = "bg-none"
  end 
end 

As an application grows, this logic also grows. Your controller actions get cluttered with needless instance variables, or they are forgotten about. Logic gets added to this bg_class and the cascading effect is a find and replace.

Naive approach: Filters

With filters, we can DRY this up easy.

class PostsController < ApplicationController 
  before_action only: :index do 
    @bg_class = "bg-dark"
  end 
end 

But to me, that feels like clutter. It’s not descriptive, and we still have to define that entire block.

Instinctively, you add this to your base controller:

Naive approach: base controller

class ApplicationController < ActionController::Base 
  
  private 

  def set_bg_class(color)
    @bg_class = "bg-dark"
  end
end

And then call it with our before_filter:

class PostsController < ApplicationController 
  before_action only: :index do 
    set_bg_class("bg-dark")
  end
end 

Again, we’re defining this entire block. It seems messy.

DSL approach using filters

class ApplicationController < ActionController::Base
  def self.set_bg_class(color, options = {})
    before_action options do 
      set_bg_class(color)
    end
  end
end 
class PostsController < ApplicationController 
  set_bg_class "bg-dark", only: [:index]
end

Now we have a tidy one-liner that’s descriptive, that uses shared principles, and is flexible.

Bonus points: moving it to a module

ApplicationController becomes a dumping ground for methods. Let’s be nice to it.

In app/controllers/concerns/bg_classable.rb:

module BgClassable 
  extend ActiveSupport::Concern 

  class_methods do 
    def set_bg_class(color, options = {})
      before_action options do 
        set_bg_class(color)
      end
    end
  end 

  def set_bg_class(color)
    @bg_class = color 
  end
end

And then in our base controller:

class ApplicationController < ActionController::Base 
  include BgClassable 
end