更新日:
【Rails】 before_actionの使い方とオプションについて
before_actionとは、コントローラの全てのアクションが実行される前に何らかの処理を行う時に使用するものです。
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]
このように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には様々なフィルタが用意されている