すでにメンバーの場合は

無料会員登録

GitHubアカウントで登録 Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

登録がまだの方はこちらから

Pikawakaにログイン

GitHubアカウントでログイン Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

Rails

【Rails】 before_actionの使い方とオプションについて

ぴっかちゃん
ぴっかちゃん

before_actionとは、コントローラの全てのアクションが実行される前に何らかの処理を行う時に使用するものです。

before_actionは、各アクションの定義の前に記述します。

Controller | before_actionの使用例
1
2
3
class UsersController < ApplicationController
  before_action :メソッド名
end

こう記述することにより、コントローラの各アクションが動く前にメソッドが実行され、その後各アクションが実行されます。

before_actionの使い方

この章では、before_actionの使い方について解説します。

アクションを実行する前に処理を実行したい場合

authenticate_user!は、ログインしていない場合にログインページにリダイレクトさせるヘルパーメソッドです。(※deviseで使えるようになります。)

このメソッドを下記のように指定すると、各アクションが動く前にログインしているかしていないかを判断し、ログインしていなければアクションを動かすことなくログインページが表示されるようすることができます。

コントローラー
1
before_action :authenticate_user!

また下記のように同じdeviseのヘルパーメソッドであるuser_singed_in?と「unless」を使ってユーザーがサインインしていない時にはルートパスを表示させるメソッドを作ったとします。

コントローラー
1
2
3
4
5
private

def redirect_root
  redirect_to root_path unless user_signed_in?
end

このメソッドはこのクラス内でしか使用しないので、そんな時には「privateメソッド」として定義しましょう。
こうすることによりコードの可読性がよくなるのと、このコントローラー以外で呼び出された時にエラーが出るのを防ぐことができます。

このメソッドをbefore_actionを使って下記のようなコードを書きました。

コントローラー
1
before_action :redirect_root, except: :index

こうするとindexアクション以外のアクションが動く前にユーザーがログインしていなければルートページが表示されるようになります。(※exceptオプションについては後述します。)

つまりindexページ以外はログインしていないと表示されないという機能を実装することができたわけです。
このようにログイン機能があるアプリでbefore_actionはよく使われます。

同じ記述の処理をまとめたい場合

各アクション内で重複しているコードがあった場合、それをメソッド化して各アクションが動く前にbefore_actionでそのメソッドを実行させたりします。

例えばコントローラーに下記の記述があるとします。

コントローラー
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class MessagesController < ApplicationController

  def index
    @messages = Message.all
  end

  def show
    @message = Message.find(params[:id])
  end

  def new
    @message = Message.new
  end

  def create
    Message.create(message_params)
  end

  def edit
    @message = Message.find(params[:id])
  end

  def update
    message = Message.find(params[:id])
    message.update(message_parmas)
  end

  def destroy
    message = Message.find(params[:id])
    message.destroy
  end

end

この時、showアクションとeditアクション内に同じコードがあることがわかります。

コントローラー
1
@message = Message.find(params[:id])

railsはなるべくコードを重複させないという原則があります。
ですので同じコードがあった場合はメソッド化しておきます。
今回もこの重複している部分をメソッド化してみましょう。

コントローラー
1
2
3
def set_message
  @message = Message.find(params[:id])
end

これでメソッド化できました。
これをbefore_actionで呼び出せばそれぞれのアクション内のコードを消せますね。

コントローラー
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class MessagesController < ApplicationController

before_action :set_message

  def index
    @message = Message.all
  end

  def show
  end

  def new
    @message = Message.new
  end

  def create
    Message.create(hoge_params)
  end

  def edit
  end

  def update
    message = Message.find(params[:id])
    message.update(hoge_parmas)
  end

  def destroy
    message = Message.find(params[:id])
    message.destroy
  end

  private

  def set_message
    @message = Message.find(params[:id])
  end

end

このようなコードになりました。
またupdateアクションとdestroyアクションの中にも重複するコードがあるので、こちらも同じようにメソッド化してbefore_actionで呼び出すことができます。

上の例だとset_messageメソッドはshowアクションとeditアクションでしか必要がありませんね。

その為、この2つのアクションが動くときだけ実行させたいです。次のオプションを利用します。

before_actionのオプション

before_actionには様々なオプションを付けることができます。

onlyオプション

特定のアクションのときだけbefore_actionを使いたい場合はonlyを使います。
今回だと下記のように記述します。

コントローラー
1
before_action :set_message, only: [:show, :edit]

シンボル型ではなく文字列として指定することもできます。

コントローラー
1
before_action :set_message, only: ["show", "edit"]

この2つのコードは%記法を使って下記のように書くこともできます。

コントローラー
1
2
3
4
5
# シンボル型で定義
before_action :set_message, only: %i[show edit]

# 文字列で定義
before_action :set_message, only: %w[show edit]
%記法(パーセント記法)のポイント
  1. コードをシンプルに記述することができる記法です。
  2. %iはシンボルの配列を作り出すことができます。
  3. %wとすると文字列の配列を作り出すことができます。

記述するコードが減るので、可読性が上がったりタイプミスによるエラーが出る確率を減らすことができます。

このようにonlyを使うと指定したアクションが動く前にbefore_actionで指定したメソッドが実行されます。

exceptオプション

特定のアクションのときだけbefore_actionを使いたくない場合はexceptを使います。
今回だと下記のように記述します。

コントローラー
1
before_action :set_message, except: [:index, :new, :create, :update, :destroy]

複数指定する場合は可読性がよくないのでonlyを使うことが多いです。

ifオプション

ifを使うと特定の条件の時にだけbefore_actionを実行させることができます。

コントローラー
1
before_action :set_message, if: :メソッド名

上のように記述するとメソッドの返り値がtrueの時だけbefore_actionを実行させることができます。

unlessオプション

unlessを使うと特定の条件の時にだけbefore_actionを実行させることができます。
上のifの反対のことができます。

コントローラー
1
before_action :set_message, unless: :メソッド名

このように記述するとメソッドの返り値がfalseの時だけbefore_actionを実行させることができます。

procで条件を指定

上のようにifやunlessで条件を指定するときはprocを使うとメソッドではなく、一つのコードを指定することができます。

コントローラー
1
before_action :メソッド名, if: proc { user_signed_in? && current_user.id == 1 } 

メソッド名だとどのようなコードが書かれているかはいちいちメソッドを確認しなければなりませんが、procを使うと直接コードを指定できるので可読性が上がるというメリットがあります。

「&&」などの論理演算子については、「論理演算子の使い方」を参考にしてください。

lambdaで条件を指定

procを使うと直接コードを指定できましたが、lambdaを使って定義することもできます。

コントローラー
1
before_action :メソッド名, if: -> { user_signed_in? && current_user.id == 1 } 

上のコードは下記のコードと全く同じになります。

コントローラー -->
1
before_action :メソッド名, if: proc { user_signed_in? && current_user.id == 1 } 

このようにオプションを使うとさらに条件を絞って実行させることができます。

複数のメソッドを指定する方法

before_actionに複数のメソッドを定義したいときは下記のように定義します。

コントローラー
1
before_action :メソッドA, メソッドB, メソッドC

また複数行で書くこともできます。

コントローラー
1
2
3
before_action :メソッドA
before_action :メソッドB
before_action :メソッドC

どちらでも記述できるメリットを活かし、ジャンルごとに分けてあげると可読性が上がります。

1
2
before_action :set_user, :set_message # インスタンス変数のセット系
before_action :check_user, :check_published_time # その他のメソッド系

引数が必要なメソッドを指定する方法

before_actionに指定するメソッドが引数を必要としている場合は下記のように記述します。

コントローラー
1
before_action -> { メソッド(引数) }

通常の時と違うので注意しましょう。

定義したbefore_actionを使いたくない時

この記事の最初に説明をしたdeviseのヘルパーメソッドであるauthenticate_user!をbefore_actionで指定する場合、ApplicationControllerに記述する場合があります。
ApplicationControllerは全てのコントローラーが継承しているため、一部のコントローラーではbefore_actionが動いて欲しくない時があります。

そんな時に使うのがskip_before_actionです。

skip_before_action

ApplicationControllerで次のように記述したとします。

コントローラー
1
2
3
class ApplicationController < ActionController::Base
  before_action :hogehoge
end

ですが、HogesControllerではbefore_action :hogehogeが動いて欲しくないときは下記のように記述します。

コントローラー
1
2
3
class HogesController < ApplicationController
  skip_before_action :hogehoge
end

indexアクションだけ無効化したいときは同じようにonlyオプションを使えばOKです。

コントローラー
1
2
3
class HogesController < ApplicationController
  skip_before_action :hogehoge, only: :index
end

その他のフィルタの説明

フィルタとは、コントローラにあるアクションの直前や直後、または直前と直後の両方に実行されるメソッドのことをいいます。

before_actionもフィルタの一つです。
rails4以前はbefore_filterという名前で使われていました。

railsではbefore_action以外にもフィルタが用意されています。
他にどういうフィルタがあるか確認してみましょう。

after_action

after_actionは名前の通り各アクションが動いた後に何かしらのメソッドを実行したい時に使用します。

コントローラー
1
2
after_action :hoge
# 各アクションが動いた後にhogeメソッドが実行される

アクションが実行された後にafter_actionが実行されるので、エラーなどにより各アクションが実行されなかった場合は実行されないので注意しましょう。

around_action

around_actionは各アクションが動く前と動いた後に実行する処理を指定することができます。
注意点としてaround_actionで指定するフィルタ内の中で必ずyieldを実行することにより、関連付けられたアクションを実行する必要があります。

コントローラー
1
2
3
4
5
6
7
8
9
10
11
12
13
class HogesController < ApplicationController
  around_action :hoge

# 略

  private

    def hoge
      アクション前に動くコード
      yield  # アクションが実行
      アクション後に動くコード
    end
end

このようにフィルタを使うことによってある条件の時にだけ動いて欲しいメソッドなどを指定することができます。

また、頭にskip_をつけるとskip_before_actionと同じくフィルタ処理を行わないようにすることができます。

ぴっかちゃん

Ruby on Railsを体系的に学びたい!という方は、複数人での開発体制やテストコードの書き方など実際の開発現場での役に立つこちらの参考書が良いでしょう。

before_actionを使うときの注意点

ここでbefore_actionを使うときに気をつけることを2点紹介します。

親コントローラーに定義をまとめよう

親コントローラーが同じである複数のコントローラーでprivate以下に同じメソッドを定義しているときは親コントローラーで定義するようにしましょう。

下記の例だとMessagesコントローラーとUsersコントローラーはApplicationコントローラーを継承しています。
そしてMessagesコントローラーとUsersコントローラーではset_categoriesメソッドを定義してbefore_actionで呼び出しています。

コントローラー
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Messagesコントローラー
class MessagesController < ApplicationController
  before_action :set_categories

# 略

  private

  def set_categories
    @categories = Category.find(params[:id])
  end

end

# Usersコントローラー
class UsersController < ApplicationController
  before_action :set_categories

  # 略

  private

  def set_categories
    @categories = Category.find(params[:id])
  end

end

このような時は親コントローラーを継承していれば親コントローラーで定義したprivateメソッドを呼び出すことができます。
こうすることにより親コントローラーを継承したコントローラー全てでこのメソッドが使えるので、より少ない記述でコードを書くことができます。

コントローラー
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Applicationコントローラーに定義する
class ApplicationController < ActionController::Base

# 略

  private

  def set_categories
    @categories = Category.find(params[:id])
  end

end

# Messagesコントローラー
class MessagesController < ApplicationController
  before_action :set_categories
end

# Usersコントローラー
class UsersController < ApplicationController
  before_action :set_categories
end

使いすぎに注意しよう

もう一つの注意点としてbefore_actionを多用しすぎるとアクションごとに何が起こってるかわかりづらくなったりしてしまいます。

コントローラー
1
2
3
4
  before_action :set_user, only: [:show, :edit, :update, :destroy]
  before_action :set_users, only: [:index]
  before_action :set_item, only: [:new, :edit]
  before_action :set_hoge, only: [:new, :create, :edit, :update]

またいちいちメソッドを確認したりする必要があるため、かえってコードの見通しが悪くなったりすることもあります。
非常に便利なものですが、このようなデメリットもあるので注意して使いましょう!

この記事のまとめ

  • before_actionは、各アクションが動く前に何らかのメソッドを実行させたい時に使う
  • オプションを使うことによりさらに細かく条件を指定することが出来る
  • before_actionの他にもrailsには様々なフィルタが用意されている