~ Dmitry Shvetsov
Navigate back to the homepage

An alternative to Rails `before_action`

Dmitry Shvetsov
January 29th, 2017 · 1 min read

Rails `before_action` is a good tool but often used incorrectly. In this article, I will show what is wrong and my remedy for `before_action` abuse.

Photo by Waranont (Joe) on (Unsplash)[“https://unsplash.com/s/photos/before?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText”]

Let’s assume that we have a task.

There is a Ruby on Rails project. The task is to determine whether a page visitor has access to a controller method before the method execution. The ideal task for a controller filter, now called before_action in Rails. But I need the data from the filter in the controller method itself. I do not want to make a second request for the same data again. I do not want to clutter up the session and do not want to assign an instance variable in the controller filter.

With the constraint above I produced the solution below:

1# Models: User, CustomerSpecialOffer, SpecialOffer
2#
3# User has_many CustomerSpecialOffer as customer
4# CustomerSpecialOffer belongs_to customer
5# CustomerSpecialOffer belongs_to SpecialOffer
6# SpecialOffer has_many CustomerSpecialOffer
7#
8className SpecialOfferController < ApplicationController
9 def show
10 customer_special_offer_code do |offer_code|
11 special_offer = SpecialOffer.find(params[:id])
12 render 'show', locals: { special_offer: special_offer, offer_code: offer_code }
13 end
14 end
15
16 def no_offers
17 end
18
19 private
20
21 def customer_special_offer_code
22 offer_code = CustomerSpecialOffer
23 .where(special_offer_id: params[:id], customer: current_user)
24 .select(:offer_code)
25 .first
26
27 if offer_code
28 yield(offer_code)
29 else
30 redirect_to 'no_offers'
31 end
32 end
33end

The #customer_special_offer_code block acts as a filter and at the same time as a source of the required data offer_code.

I did a check, acquire the data, set no instance variables in the controller scope and have no extra database queries. Job done!


Very often the before_action method is used so that it only harms the code. For example, this “Best practice” is wrong in my opinion in two reasons:

  • the code has become harder to understand, you should look for more than one place to understand what’s going on
  • controller inheritance is no longer an option, together with inherited methods, you will get all filters; although I do not recommend to consider inheritance with any resource controllers, ApplicationController, AdminController, and similar controllers are the exception.

Following the statements above, I do not recommend to do this:

1className UsersController < ApplicationController
2 before_action :set_user, only: [:show, :edit, :update]
3
4 def show
5 end
6
7 def edit
8 end
9
10 def update
11 if @user.update(user_params)
12 redirect_to @user, notice: 'User was successfully updated.'
13 else
14 render :edit
15 end
16 end
17
18 private
19
20 def set_user
21 @user = User.find(params[:id])
22 end
23
24 def user_params
25 params.require(:user).permit(:name, :email, :password,
26 :password_confirmation)
27 end
28end

As an alternative, I suggest using a block with an explicit returned value. Here is what I mean:

1className UsersController < ApplicationController
2 def show
3 find_user do |user|
4 render :show, locals: { user: user }
5 end
6 end
7
8 def edit
9 find_user do |user|
10 render :edit, locals: { user: user }
11 end
12 end
13
14 def update
15 find_user do |user|
16 if user.update(user_params)
17 redirect_to user, notice: 'User was successfully updated.'
18 else
19 render :edit, locals: { user: user }
20 end
21 emd
22 end
23
24 private
25
26 def find_user
27 user = User.find(params[:id])
28 yield(user)
29 end
30
31 def user_params
32 params.require(:user).permit(:name, :email, :password,
33 :password_confirmation)
34 end
35end

In my opinion, the block method more clean and simple solution for the data instantiation problem rather than before_action approach.


If you have opinion, suggestions, feedback on this topic please drop a comment. I will be happy to have a talk about it.

More articles from Dmitry Shvetsov

How to Speed Up Code Review Process With Code Review Time

This is a chapter from Team Lead 101: Manage and Grow Engineering Teams in Small Startups ebook.  Get your copy here. Photo by Margarida…

July 25th, 2020 · 1 min read

One-on-One Meetings: The Most Important Tool For Engineering Team Leader

This is a chapter Team Lead 101: Manage and Grow Engineering Teams in Small Startups book.  Click here to learn more Photo by Christina…

July 25th, 2020 · 4 min read
© 2017–2020 Dmitry Shvetsov
Link to $https://twitter.com/iamdidev