Railsでは、コントローラーのアクションを実行する前に特定の処理を実行するためのフィルターとしてbefore_actionが用意されています。
本記事では、初心者向けにbefore_actionの基本的な使い方やオプションについて解説します。
before_actionとは
before_action
はRailsのコントローラに用意されたフィルターの一つで、アクションを実行する前に特定の処理を実行します。
例えば、ログインしていないユーザがアクセスできないページを実装する場合、before_action
を使用してログイン状態を確認する処理を実行できます。
before_actionはオプションを指定して適用するコントローラのアクションを制限できます。
コントローラは複数のアクションを実装できますが、その一部のアクションにのみbefore_actionを適用したい場合はonly
オプションを使用します。
before_action
を使いこなすと処理の共通化を実現できます。結果としてアプリケーションの開発効率の向上が見込めます。
さらに、フィルターを使用することで、アプリケーションのセキュリティを向上できます。
例えば、不正なリクエストをブロックするフィルターをbefore_actionとして実装すると、アプリケーションのセキュリティを強化することができます。
基本的な使い方
before_action
を使用するには、コントローラー直下で以下のように記述します。
before_action :メソッド名
例を挙げます。
class SomeController < ApplicationController
before_action :some_method # ①
private
def some_method # ②
...
end
end
コントローラ内でメソッドを定義し(②)、before_action の引数では、メソッド名のシンボルを指定します(①)。
①の定義を行うと、SomeControllerの全てのアクションが実行される前に、some_method メソッドを実行します。
before_actionのオプション
before_actionは以下のオプションを指定できます。
オプション | 意味 |
---|---|
:only | 適用したいアクションを指定 |
:except | 適用しないアクションを指定 |
:if | 適用する条件を指定 |
:unless | 適用しない条件を指定 |
それぞれ解説します。
onlyオプション
コントローラには複数のアクションを実装しますが、一部のアクションにのみbefore_actionを適用したい場合があります。
一部のアクションにのみ適用したい場合はonly
オプションを指定します。
class UsersController < ApplicationController
before_action :authenticate_user, only: [:edit, :update]
def index
@users = User.all
end
def edit
@user = User.find(params[:id])
end
def update
@user = User.find(params[:id])
if @user.update(user_params)
redirect_to @user
else
render :edit
end
end
private
def authenticate_user
unless user_signed_in?
redirect_to new_user_session_path
end
end
def user_params
params.require(:user).permit(:name, :email)
end
end
上記の例では、edit
アクションとupdate
アクションにのみauthenticate_user
メソッドが実行されます。
exceptオプション
onlyオプションの逆で、特定のアクションにはフィルターを適用しないようにするにはexcept
オプションを使用します。
class UsersController < ApplicationController
before_action :authenticate_user, except: [:index, :show]
def index
@users = User.all
end
def show
@user = User.find(params[:id])
end
private
def authenticate_user
unless user_signed_in?
redirect_to new_user_session_path
end
end
end
上記の例では、index
アクションとshow
アクションにはauthenticate_user
メソッドを実行しません。
ifオプション
条件を指定して特定のアクションにのみフィルターを適用するにはif
オプションを使用できます。
class UsersController < ApplicationController
before_action :authenticate_user, if: -> { params[:action] == 'edit' }
def index
@users = User.all
end
def edit
@user = User.find(params[:id])
end
def update
@user = User.find(params[:id])
if @user.update(user_params)
redirect_to @user
else
render :edit
end
end
private
def authenticate_user
unless user_signed_in?
redirect_to new_user_session_path
end
end
def user_params
params.require(:user).permit(:name, :email)
end
end
上記の例では、edit
アクションにのみauthenticate_user
メソッドが実行されます。if
オプションには、条件式をProcオブジェクトで指定しています。params[:action]
は、現在のアクション名を取得するために使用されています。
unlessオプション
ifオプションの逆で、適用しない条件を指定するunless
オプションもあります。以下は、unless
オプションを使用した例です。
class UsersController < ApplicationController
before_action :authenticate_user, unless: -> { params[:action] == 'index' }
def index
@users = User.all
end
def show
@user = User.find(params[:id])
end
private
def authenticate_user
unless user_signed_in?
redirect_to new_user_session_path
end
end
end
上記の例では、index
アクション以外のアクションに対して、authenticate_user
メソッドが実行されます。
重要:before_action内でredirect_toを実行するとアクションをスキップする
before_action
内でredirect_to
を実行すると後続のアクションをスキップします。
これは初心者に見落とされがちな大切な性質です。
例えば、以下の例を考えます。
class UsersController < ApplicationController
before_action :authenticate_user
def index
@users = User.all
end
private
def authenticate_user
unless user_signed_in? # ①
redirect_to new_user_session_path # ②
end
end
end
上記の例では、index
アクションを実行する前に、authenticate_user
メソッドが実行されます。
authenticate_user
メソッドは、user_signed_in?
メソッドがfalse
を返した場合(①)、new_user_session_pathにリダイレクトします(②)。
もし②が実行された場合はindexアクションは実行されずスキップされます。
以下は、before_action
でアクションをスキップしたときのRailsサーバのログの例です。
Started GET "/users" for 127.0.0.1 at 2021-10-01 12:00:00 +0900
Processing by UsersController#index as HTML
Redirected to <http://localhost:3000/login>
Filter chain halted as :authenticate_user rendered or redirected ← ③
Completed 302 Found in 10ms (ActiveRecord: 0.0ms | Allocations: 682)
上記のコード例でauthentiacte_userでリダイレクトが発生した場合、③で「Filter chain halted」と記載されており、停止(halt)したことが分かります。
これは後続のアクションを実行せずに停止したと報告しています。
before_action内でリダイレクトが発生した場合は後続のアクションが実行されないことを覚えておきましょう。
≫ redirect_to によるリダイレクトの方法を徹底解説【Ruby on Rails】
リダイレクトが発生した場合に後続アクションをスキップすると説明しました。
しかし正確には「レスポンス内容が生成される」と後続アクションをスキップします。
レスポンス内容を作成するのは redirect_to の他に、render があります。
つまり実はrenderが実行されても後続アクションをスキップします。
ただし、before_actionはアクションの実行可否を判定する目的で使う場面が多いです。
実行不可ならアクションを実行せずに別のページに飛ばす、といった使い方が多いでしょう。
そのため、実際の使用法としてbefore_actionではrenderよりもredirect_toを使うことが多いです。
before_actionの典型的な使用例
before_actionをどのようなときに利用すればいいのか、典型的な使用例を挙げます。
よく使う例として以下を説明します。
- インスタンス変数の初期化
- アクションを実行する権限をチェックする
インスタンス変数の初期化
before_action
を使用してインスタンス変数の初期化処理を共通化できます。
例えば以下のコードを考えます。
class UsersController < ApplicationController
def show
@user = User.find(params[:id]) # ①
end
def edit
@user = User.find(params[:id]) # ②
end
def update
@user = User.find(params[:id]) # ③
if @user.update(user_params)
redirect_to @user, notice: 'User was successfully updated.'
else
render :edit
end
end
def destroy
@user = User.find(params[:id]) # ④
@user.destroy
redirect_to users_url, notice: 'User was successfully destroyed.'
end
end
上記コードでは、show, edit, update, destroy で同じインスタンス変数@userを同じ処理(User.find(params[:id]))で初期化されています。(①、②、③、④)
同じ処理をprivateメソッドとしてまとめて、before_actionで設定すると以下のコードになります。
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy] # ①
def show
end
def edit
end
def update
if @user.update(user_params)
redirect_to @user, notice: 'User was successfully updated.'
else
render :edit
end
end
def destroy
@user.destroy
redirect_to users_url, notice: 'User was successfully destroyed.'
end
private
def set_user # ②
@user = User.find(params[:id])
end
end
set_userメソッドとして初期化コード(@user = User.find(params[:id]))をまとめました。(②)
その上で、onlyオプションを使ってshow
、edit
、update
、destroy
アクションのみに適用しています。(①)
このように共通で使うインスタンス変数の初期化処理をまとめると、重複が減ってメンテナンスがしやすいコードになります。
アクションを実行する権限をチェックする
before_action
を使用してアクションを実行する権限をチェックできます。
例えば、管理者権限を持つユーザのみがアクセスできるページを実装する場合を考えます。before_action
を使用するとユーザが管理者権限を持つかどうかをチェックする処理を実行できます
不正なアクセスを防止し、アプリケーションのセキュリティを強化につながります。
以下がコード例です。
class Admin::UsersController < ApplicationController
before_action :authenticate_admin!
def index
@users = User.all
end
private
def authenticate_admin!
unless current_user.admin?
redirect_to root_path, alert: "You don't have access to this page."
end
end
end
上記の例では、Admin::UsersController
にログインしているユーザが管理者権限を持っているかどうかをチェックし、持っていない場合にはroot_pathにリダイレクトします。
これはリダイレクトによる後続アクションのスキップという性質をつかった典型例です。
admin?メソッドについてはusersテーブルにboolean型のadminカラムを用意するといいでしょう。
まとめ
アクションの前処理を実現するbefore_actionを解説しました。
重要なポイントは以下の通りです。
- 複数のアクションで重複している処理を共通化できる
- redirect_toを実行すると後続のアクションをスキップできる
before_actionをマスターして効率的な開発を行ってください。