コガミツlog

福岡在住エンジニアのブログ

prepend_before_action を連続で定義した時の実行順番

結論

複数ある場合は、下から順番にprepend_before_actionが実行される

きっかけ

controllerでbefore_actionを複数個書くと当然ファイルの上から実行されます。

prepend_before_action を複数個定義する要件がありbefore_actionと同じ感覚で実行されて欲しい順に記載したのですが、意図した順番とは逆の結果になってしまいました。

調査

少なくとも自分が調べた限り直接的なドキュメントはなかったのでソースコード読みました。

prepend_before_actionの定義は以下のソースコードです。

github.com

...途中省く

[: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

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

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

定義のところだけ抜き出します。

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

prependの時はset_callbackメソッドのオプションとして、prepend: trueを追加しているみたいです。

続いてset_callbackメソッドのドキュメントを見てみます。

api.rubyonrails.org

:prepend - If true, the callback will be prepended to the existing chain rather than appended.

の記載があります。

既にあるcallbackチェーンの後ろに追加するのではなく先頭に付ける、と書いてありました。

つまり、prepend_before_action を複数個定義すると、 ファイルの上からcallbackチェーンの先頭に追加されていくので、実行時は一番先頭である最後のprepend_before_actionが実行されるようです。

つまりこんな感じになります。

class SomeController < ApplicationController
  prepend_before_action :first_prepend
  prepend_before_action :second_prepend

  def hoge_action
    # ...
  end

  private

  def first_prepend
    puts "First"
  end

  def second_prepend
    puts "Second"
  end
end

この場合結果としては、こうなりました。

Second
First