【Rails】before_actionの使い方を徹底解説!【初心者向け】

before_actionの使い方を徹底解説!Ruby on Rails

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オプションを使ってshoweditupdatedestroyアクションのみに適用しています。(①)

このように共通で使うインスタンス変数の初期化処理をまとめると、重複が減ってメンテナンスがしやすいコードになります。

アクションを実行する権限をチェックする

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をマスターして効率的な開発を行ってください。

タイトルとURLをコピーしました