LoginSignup
17
7

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-12-14

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

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

このメソッドは引数として渡された 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ドキュメントより引用

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

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

             set_callback(:process_action, callback, name, options)

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

process_action

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

    def process_action(*args)
      run_callbacks(:process_action) do
        super
      end
    end

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

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

まとめ

  • 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/

17
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
7