基本

Ruby on RailsのApplicationControllerで共通処理を定義する

Ruby on Railsでは、すべてのコントローラーがApplicationControllerを継承しています。このため、ApplicationControllerに定義したメソッドやコールバックは、アプリケーション内のすべてのコントローラーで自動的に利用できます。認証処理やロケール設定など、アプリケーション全体で共通する処理を集約するのに最適な場所です。

基本的な使い方

ApplicationControllerbefore_actionやメソッドを定義すると、すべてのコントローラーで自動的に適用されます。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :set_locale

  private

  def set_locale
    I18n.locale = params[:locale] || I18n.default_locale
  end
end

上記の例では、すべてのリクエストでset_localeメソッドが実行され、言語設定が適用されます。個別のコントローラーにはこの処理を書く必要がありません。

認証処理の定義

最も一般的な使い方は、ログインユーザーの取得と認証チェックの定義です。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  helper_method :current_user, :logged_in?

  private

  def current_user
    @current_user ||= User.find_by(id: session[:user_id])
  end

  def logged_in?
    current_user.present?
  end

  def require_login
    unless logged_in?
      redirect_to login_path, alert: "ログインが必要です"
    end
  end
end

helper_methodcurrent_userlogged_in?を宣言しているため、ビューでもこれらのメソッドを使えます。各コントローラーでは必要に応じてbefore_action :require_loginを追加するだけで認証チェックが有効になります。

app/controllers/posts_controller.rb
class PostsController < ApplicationController
  before_action :require_login, except: [:index, :show]

  def index
    @posts = Post.all
  end

  def create
    @post = current_user.posts.build(post_params)
    # current_userはApplicationControllerで定義済み
  end
end

エラーハンドリング

アプリケーション全体で共通のエラーハンドリングを定義できます。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
  rescue_from ActionController::ParameterMissing, with: :bad_request

  private

  def record_not_found
    render file: Rails.root.join("public/404.html"),
           status: :not_found, layout: false
  end

  def bad_request(exception)
    render json: { error: exception.message }, status: :bad_request
  end
end

rescue_fromを使うと、特定の例外が発生したときにカスタムの処理を実行できます。個別のコントローラーでbegin/rescueを書く必要がなくなります。

コントローラーの継承階層

管理画面など、特定のコントローラー群で共通する処理がある場合は、中間の基底コントローラーを作成できます。

app/controllers/admin/base_controller.rb
class Admin::BaseController < ApplicationController
  before_action :require_admin
  layout "admin"

  private

  def require_admin
    unless current_user&.admin?
      redirect_to root_path, alert: "管理者権限が必要です"
    end
  end
end
app/controllers/admin/users_controller.rb
class Admin::UsersController < Admin::BaseController
  # ApplicationControllerとAdmin::BaseControllerの両方の処理を継承
  def index
    @users = User.all
  end
end

継承チェーンはAdmin::UsersController → Admin::BaseController → ApplicationController → ActionController::Baseとなり、すべてのコールバックが順番に適用されます。

ポイント

ApplicationControllerには本当にすべてのコントローラーで必要な処理のみを定義しましょう。特定のコントローラー群でのみ必要な処理は、中間の基底コントローラーやConcernsに分離するのがベストプラクティスです。

注意

ApplicationControllerにAPIコントローラーと通常のコントローラーの両方に適用されるコールバックを書くと、API側でCSRFトークン検証エラーなどが発生することがあります。APIコントローラーはActionController::APIを継承するなど、適切に分離してください。

まとめ

  • ApplicationControllerに定義した処理はすべてのコントローラーで有効
  • 認証(current_user)やロケール設定など共通処理を集約する
  • rescue_fromでアプリケーション全体のエラーハンドリングを定義できる
  • helper_methodでビューからもメソッドを呼び出せるようにする
  • 管理画面用など中間の基底コントローラーで階層的に共通処理を管理する
  • すべてのコントローラーに必要な処理のみを定義し、肥大化を避ける