Posted on

Controller filters are a common sight in Ruby on Rails controllers. The before_filter specifically can be used to halt the request cycle. This is useful to prevent unauthorized access to controller actions, but it’s also very often used to load database records.

Take a look at this controller:

class TreesController
  before_action :authenticate_user!
  before_action :find_forest
  before_action :find_trees
  before_action :find_tree, except: [:index, :new, :create]

  def index
  end

  def show
  end

  def edit
  end

  def update
    if @tree.update(tree_params)
      redirect_to(@tree)
    else
      render('edit')
    end
  end

  private

  def find_forest
    @forest = Forest.find(params[:forest_id])
  end

  def find_trees
    @trees = @forest.trees
  end

  def find_tree
    @tree = @trees.find(params[:id])
  end
end

The before_action filters have been used to make the controller DRY. The forest and trees are loaded by the filters and assigned to instance variables.

The views for show and edit have access to the @tree instance variable, but this isn’t obvious. You have to look at the controller and connect the dots to figure out which variables are available where.

Perhaps the controller has become too DRY. The filters are coupled to the controller actions in a very weird way. In fact, the :find_trees and :find_tree filters are coupled to each other as well, because :find_tree depends on :find_trees. In terms of the problem domain, it becomes hard to see the forest through the trees (pun intended).

In other words, clarity has been sacrificed for the sake of DRY-ness. Don’t get me wrong, DRY is a great concept, but not when it turns your Rails controller into a complicated puzzle!

In my opinion, before filters should only ever be used to do what they do best: halting the request cycle. They should not be used for data loading and preparing state.

Applying that to our controller would result in something like this:

class TreesController
  before_action :authenticate_user!

  def index
    @trees = find_forest.trees
  end

  def show
    @tree = find_tree
  end

  def edit
    @tree = find_tree
  end

  def update
    @tree = find_tree

    if @tree.update(tree_params)
      redirect_to(@tree)
    else
      render('edit')
    end
  end

  private

  def find_tree
    find_forest.trees.find(params[:id])
  end

  def find_forest
    Forest.find(params[:forest_id])
  end
end

The filters which were previously loading data have been replaced with plain method calls from the controller actions. Now it’s immediately obvious which data is loaded where.


If you like this post, follow me:


Or share this post: