【Rails】before_actionの使い方を徹底解説!

Rails

before_actionとは

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

コントローラーの各アクションが実行される前に、何かしらのメソッドを実行したい時があります。
そんな時にbefore_actionを使うと指定したコードを各アクションが動く前に実行させることができます。

before_actionの使い方
リンクをコピーしました

before_actionはコントローラー内に記述します。

ruby
1
2
3
4
5
class UsersController < ApplicationController

  before_action :メソッド名

end

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

それでは実際before_actionは一体どのような時に使うのでしょうか?
例をあげてみます。

アクションを実行する前に処理を実行したい場合
リンクをコピーしました

deviseで使えるようになるヘルパーメソッドで「authenticate_user!」があります。
これはログインしていないとログインページにリダイレクトさせるメソッドです。

ruby
1
before_action :authenticate_user!

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

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

ruby
1
2
3
4
5
private

def redirect_root
  redirect_to root_path unless user_signed_in?
end

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

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

ruby
1
before_action :redirect_root, except: :index

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

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

同じ記述の処理をまとめたい場合
リンクをコピーしました

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

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

ruby
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アクション内に同じコードがあることがわかります。

ruby
1
@message = Message.find(params[:id])

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

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

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

ruby
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を使います。
今回だと下記のように記述します。

ruby
1
before_action :set_message, only: [:show, :edit]

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

ruby
1
before_action :set_message, only: ["show", "edit"]

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

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

# 文字列で定義
before_action :set_message, only: %w[show edit]

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

exceptオプション
リンクをコピーしました

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

ruby
1
before_action :set_message, except: [:index, :new, :create, :update, :destroy]

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

ifオプション
リンクをコピーしました

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

ruby
1
before_action :set_message, if: :メソッド名

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

unlessオプション
リンクをコピーしました

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

ruby
1
before_action :set_message, unless: :メソッド名

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

procで条件を指定
リンクをコピーしました

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

ruby
1
before_action :メソッド名, if: proc { user_signed_in? && current_user.id == 1 } 

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

lambdaで条件を指定
リンクをコピーしました

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

ruby
1
before_action :メソッド名, if: -> { user_signed_in? && current_user.id == 1 } 

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

ruby
1
before_action :メソッド名, if: proc { user_signed_in? && current_user.id == 1 } 

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

複数のメソッドを指定する方法
リンクをコピーしました

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

ruby
1
before_action :メソッドA, メソッドB, メソッドC

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

ruby
1
2
3
before_action :メソッドA
before_action :メソッドB
before_action :メソッドC

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

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

引数が必要なメソッドを指定する方法
リンクをコピーしました

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

ruby
1
before_action -> { メソッド(引数) }

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

定義したbefore_actionを使いたくない時
リンクをコピーしました

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

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

skip_before_action
リンクをコピーしました

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

ruby
1
2
3
class ApplicationController < ActionController::Base
  before_action :hogehoge
end

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

ruby
1
2
3
class HogesController < ApplicationController
  skip_before_action :hogehoge
end

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

ruby
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は名前の通り各アクションが動いた後に何かしらのメソッドを実行したい時に使用します。

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

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

around_action
リンクをコピーしました

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

ruby
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と同じくフィルタ処理を行わないようにすることができます。

before_actionを使うときの注意点
リンクをコピーしました

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

親コントローラーに定義をまとめよう
リンクをコピーしました

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

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

ruby
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メソッドを呼び出すことができます。
こうすることにより親コントローラーを継承したコントローラー全てでこのメソッドが使えるので、より少ない記述でコードを書くことができます。

ruby
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を多用しすぎるとアクションごとに何が起こってるかわかりづらくなったりしてしまいます。

ruby
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には様々なフィルタが用意されています