Note: This is part of the Production vs Tutorial Code Series.



Like instance variables, before_actions have their uses, but they tend to be overused.

The Problem: Loading Data

Here is an example that could have come straight from a tutorial:

class NotesController < ApplicationController
  before_action :load_note_list, only: %i(index)

  def index; end

private

  def load_note_list
    @notes = Notes.for_user(current_user).to_a
  end
end

Ask yourself: why is this before_action necessary?

To make sure the query only runs once.

Ok, but we know how to memoize data, so let’s handle that in a different way.

class NotesController < ApplicationController
  helper_method :notes

  def index; end

protected

  def notes
    @notes ||= Notes.for_user(current_user).to_a
  end
end

There. Now, when we call notes multiple times from the view, it will only do the query once. But if, by some future view logic, we never need to display the list of notes, we also don’t have to run the query!

What You Should Do Before You Take Action

In general my policy is to avoid before_action. However, there are some exceptions.

The most common exception is when, for multiple actions, I may need to redirect, render, or otherwise modify the response. Examples of this:

  • before_action :authenticate_user! which may redirect to a sign-in page
  • before_action :ensure_user_can_view_content which may render an error page instead of the current content

A counter example is when you only do this for one action.

Instead of:

class NotesController < ApplicationController
  before_action :setup_an_action, only: %i(an_action)

  def an_action; end
end

consider:

class NotesController < ApplicationController
  def an_action
    setup_an_action
  end
end

This makes the control flow more clear.

Another exception is when, again for multiple actions, I need to modify the session before the action code runs. This is most often used to clear temporary session keys set as part of a multi-step process.

Examples of this:

  • before_action :clear_stored_location, except: %i(some_callback_action) which ensures we don’t keep a temporary key in the session if the user abandons a process.
  • before_action :ensure_fresh_user_token which might call an API to refresh a token in the session.

As noted in the previous section, instead of before_action :load_some_data, consider using a memoized helper method. Even if you need to use this data immediately (in a valid before_action) the memoized helper method is a much better approach from a maintainability and reuse perspective.


This Series