If you have two or more render statements in your controller action, you might consider extracting some of the logic into a before action. Why? Because rendering or redirecting in a before_action will halt the request cycle and remove the need for an and return
in your controller action. This can also help keep your controller actions focused, smaller, and easier to reason about for other developers.
Let’s look at an example, where we return early in an action if a user is suspended:
class OrdersController < ApplicationController
def index
if current_user.suspended?
render json: { error: 'Your account is suspended.' }, status: :forbidden and return
end
@orders = current_user.orders
render :index
end
end
We could write this as this instead:
class OrdersController < ApplicationController
before_action :verify_active_user, only: :index
# Keeps our index method targeted and focused.
def index
@orders = current_user.orders
render :index
end
private
def verify_active_user
if current_user.suspended?
# No return necessary now, the request cycle will halt if you render/redirect in
# before_action.
render json: { error: 'Your account is suspended.' }, status: :forbidden
end
end
end
The
index
action is now focused only on its core responsibility—retrieving and rendering orders.No need for
and return
, sincerender
automatically halts execution.
Essentially we’ve simply performed the Extract Method refactoring, but with the added benefit of terminating the request early if our condition isn’t met.
A great side effect is that you can extract this logic into a concern and easily reuse it across a number of controllers:
class OrdersController < ApplicationController
include SuspendedUserProtection
def index
@orders = current_user.orders
render :index
end
end
# This is an easy approach that could be used when the action needs to be applied
# very liberally without many exceptions (where you could use `skip_before_action`)
module SuspendedUserProtection
extend ActiveSupport::Concern
included do
before_action :verify_active_user
end
def verify_active_user
if current_user.suspended?
render json: { error: 'Your account is suspended.' }, status: :forbidden
end
end
end
Alternatively, for a more flexible approach, we could use Rails’ RequestForgeryProtection as inspiration, where they wrap a before_action
with a class method called protect_from_forgery
that forwards the options to before_action
, like this:
class OrdersController < ApplicationController
include SuspendedUserProtection
# We can now specify all normal before_action options here to target this check where
# needed.
block_suspended_user only: :index
def index
@orders = current_user.orders
render :index
end
end
module SuspendedUserProtection
extend ActiveSupport::Concern
class_methods do
def block_suspended_user(options)
before_action :verify_active_user, options
end
end
def verify_active_user
if current_user.suspended?
render json: { error: 'Your account is suspended.' }, status: :forbidden
end
end
end
The Fine Print
Can this be abused? Yes. Should you do it blindly? No! But used judiciously, this can absolutely clean up bulky, convoluted controller methods. Happy refactoring!