Railsコントローラー継承におけるbefore_actionの注意点


2025年 05月 22日

SI部の toga です。

Railsアプリケーションでは、事前処理を before_action として定義し、コントローラー間で継承することができます。しかし、親クラスに定義した before_action が意図せず実行されないという事象が先日発生しました。

本記事ではその原因と解決方法について紹介します。本記事の動作環境は Rails 8.0.2 です。

背景

本来は実行されるはずの before_action がスキップされ、後続の before_action のみが実行されていることに気づきました。その結果、期待しない順序で処理が進行し、アプリケーションの動作に不整合が生じました。

原因を調査したところ、親クラスで定義された before_action と同名の before_action を子クラスでも再度宣言していたことが問題であることが判明しました。

原因

同じコントローラー内での重複した before_action の宣言

Railsの before_action は、同じコントローラー内で同一のメソッドに対する複数の宣言があると、最後の宣言が優先されるという挙動を持ちます。

同じコールバックを異なるオプションで複数回呼び出すと、最後に呼び出されたアクションコールバックの定義によって、それまでのコールバックの定義は上書きされます。
参考: https://railsguides.jp/action_controller_overview.html#before-action

class ExamplesController < ActionController::Base
  before_action :do_something_1
  before_action :do_something_2
  before_action :do_something_1

  def index; end

  private

  def do_something_1
    puts "do_something_1"
  end

  def do_something_2
    puts "do_something_2"
  end
end

私が期待していた出力順序は以下です

do_something_1
do_something_2
do_something_1

しかし、実際の出力は以下のようになります

do_something_2
do_something_1

親子コントローラー内での重複した before_action の宣言

親クラスで定義された before_action も子クラス側で上書きされるため、親クラスの before_action が実行されないという状況が生じました。

以下のような構成のコントローラーを用いて、問題の再現と挙動の確認を行いました。

class ApplicationController < ActionController::Base
  before_action :do_something_3

  private

  def do_something_3
    puts "do_something_3"
  end
end

class ConcreteController < ApplicationController
  before_action :do_something_1
  before_action :do_something_2
  before_action :do_something_3

  def index; end

  private

  def do_something_1
    puts "do_something_1"
  end

  def do_something_2
    puts "do_something_2"
  end
end

期待していた出力順序は以下の通りです

do_something_3
do_something_1
do_something_2
do_something_3

しかし、実際の出力は以下のようになりました

do_something_1
do_something_2
do_something_3

親クラスの before_action による do_something_3 は実行されておらず、子クラスで再宣言された do_something_3 のみが実行対象となっていました。

おわりに

  • 親クラスと子クラスの両方で同じ before_action を定義した場合、子クラス側の定義が優先され、親クラスの before_action は実行されなくなることを確認しました。
  • 子クラスの再宣言は上書きやオプション変更を意図したものではないので削除をして解決しました。
  • 今後の再発を防ぐためにも、親子関係における before_action の重複を静的解析で検知できるように、RuboCopのカスタムルールの導入も検討していきたいです。
< 前の記事へ 次の記事へ >