Before Action, Some Thoughts on Data Loading...
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 pagebefore_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.