Rails

railsのbefore_actionはどうやってうごいているか

Ateam Lifestyle x cyma Advent Calendar 2018 の15日目は、株式会社エイチームライフスタイルのWebエンジニア @kytiken が担当します。

before_actionと言っているのは、controllerに before_action :メソッド名 と書いたら、アクションが実行される直前に指定したメソッドを実行してくれるアレのことです。

今回の記事では before_action がどうやって動いているのかを、railsのソースコードを読むことによって、理解を深めようという趣旨で書いていきます。


バージョン

rails5.2


コードリーディング

実際にbefore_actionをメソッドを定義している場所を見てみます。

       [:before, :after, :around].each do |callback|

define_method "#{callback}_action" do |*names, &blk|
_insert_callbacks(names, blk) do |name, options|
set_callback(:process_action, callback, name, options)
end
end

https://github.com/rails/rails/blob/5-2-stable/actionpack/lib/abstract_controller/callbacks.rb#L186

before_actionを定義している部分をかいつまんで抜き出しました。

define_methodで動的にメソッドを作っています。

before_actionを実行すると _insert_callbacks を実行して、そのブロックの中で set_callback しているとわかります。

この2つのメソッドがそれぞれ何をしているかを調べていきます。


_insert_callbacks

       def _insert_callbacks(callbacks, block = nil)

options = callbacks.extract_options!
_normalize_callback_options(options)
callbacks.push(block) if block
callbacks.each do |callback|
yield callback, options
end
end

https://github.com/rails/rails/blob/5-2-stable/actionpack/lib/abstract_controller/callbacks.rb#L91

このメソッドは引数として渡された callbacks をeachでグルグル回して、 options と一緒に yield にわたすということをしています。

また第二引数としてblockが渡されているとしたら、それをcallbacksに追加して、最終的には yield に渡されます。


set_callback

このメソッドは任意のメソッドに対して、コールバックを設定できるメソッドとなります。

このメソッドの使い方は、こちらのAPIドキュメントに詳しく書いてあります。

class Audit

def before(caller)
puts 'Audit: before is called'
end

def before_save(caller)
puts 'Audit: before_save is called'
end
end

class Account
include ActiveSupport::Callbacks

define_callbacks :save
set_callback :save, :before, Audit.new

def save
run_callbacks :save do
puts 'save in main'
end
end
end

apiドキュメントより引用

https://api.rubyonrails.org/classes/ActiveSupport/Callbacks/ClassMethods.html#method-i-define_callbacks

set_callback を使ってコールバックするためには、 define_callbacks をしておく必要があるのと、 run_callbacks を実行するようにしておく必要があります。


before_actionの定義

_insert_callbacksset_callback のことがわかったところで、もう一回 before_action を見てみます。

       [:before, :after, :around].each do |callback|

define_method "#{callback}_action" do |*names, &blk|
_insert_callbacks(names, blk) do |name, options|
set_callback(:process_action, callback, name, options)
end
end

https://github.com/rails/rails/blob/5-2-stable/actionpack/lib/abstract_controller/callbacks.rb#L186

さて、before_actionの定義で、set_callbackは下記のように使われていました。

             set_callback(:process_action, callback, name, options)

https://github.com/rails/rails/blob/5-2-stable/actionpack/lib/abstract_controller/callbacks.rb#L189

process_action メソッドにコールバックをつけていることがわかります。


process_action

この process_action というメソッドは、アクションが実行されるタイミングで実行されるのですが、このモジュールの中で、 run_callbacks を実行するように上書きしています。

    def process_action(*args)

run_callbacks(:process_action) do
super
end
end

https://github.com/rails/rails/blob/5-2-stable/actionpack/lib/abstract_controller/callbacks.rb#L40

ついでにいうと、callbackが使えるように define_callbacks もやっています。

      define_callbacks :process_action,

terminator: ->(controller, result_lambda) { result_lambda.call; controller.performed? },
skip_after_callbacks_if_terminated: true

https://github.com/rails/rails/blob/5-2-stable/actionpack/lib/abstract_controller/callbacks.rb#L33


まとめ



  • process_action が実行されるタイミングで run_callback が実行される。


  • before_actionprocess_action にset_callbackするもの。

Ateam Lifestyle x cyma Advent Calendar 2018 の16日目は @kyntk さん に書いてもらう予定です。お楽しみに!

エイチームグループでは、一緒に働けるチャレンジ精神旺盛な仲間を募集しています。興味を持たれた方はぜひエイチームグループ採用サイトを御覧ください。

https://www.a-tm.co.jp/recruit/