現在携わっているプロダクトでは、ActiveAdminを利用して管理画面を開発しています。 提供されているDSLを記述することで、リソース(モデル)ごとに管理画面に必要なコントローラー、フォームなどのビューを作成することができるので便利です。

自動的にコントローラーが生成されるのですが、それら全体に対してbefore_actionなどを追加したくなる場合があります。 ここではその方法について説明します。

前提

まず前提として、ActiveAdminが生成するコントローラーは、そのRailsアプリケーションのApplicationControllerを継承します。 ですので、そこで定義されているbefore_actionが定義されていれば、それはActiveAdminのコントローラーにも適用されることになります。

以下では、アプリケーションモニタリングサービスのSentryへ送る情報を切り替える場合を例に具体的な実装を見ていきます。

Sentryにユーザー情報を送る

sentry-railsを使うと、Railsアプリケーションで例外が発生するとその例外やその時のパラメーターなどをSentryに送ることができます。 この情報に、そのときにログインしているユーザーの情報を送りたい場合があります。 以下のようにbefore_actionを追加すると、ログインしている場合にそのユーザーのIDとユーザー名、メールアドレスを送ることができるようになります。

class ApplicationController < ActionController::Base
  before_action :set_sentry_context

  private

  def set_sentry_context
    Sentry.set_user(id: current_user.id, username: current_user.name, email: current_user.email) if current_user
  end
end

ActiveAdminのときは管理者の情報を送る

このままだと、ActiveAdminの画面で例外が発生した場合にも、ユーザーとしてログインしていればその情報がSentryに送られてしまいます。 そこで、Sentryの設定でActiveAdminのリソースのコントローラーのみにbefore_actionを追加します。

config/initializers/active_admin.rb に下記の1行を追加します。

ActiveAdmin.setup do |config|
  config.before_action = :set_sentry_context_for_active_admin # Add this line
end

このset_sentry_context_for_active_admin自体は、先程と同じApplicationControllerに追加します。

class ApplicationController < ActionController::Base
  before_action :set_sentry_context

  private

  def set_sentry_context
    Sentry.set_user(id: current_user.id, username: current_user.name, email: current_user.email) if current_user
  end

  def set_sentry_context_for_active_admin
    Sentry.set_user(id: current_admin_user.id, email: current_admin_user.email) if current_admin_user
  end
end

このままでも十分に期待した通りの動作をするのですが、set_sentry_contextset_sentry_context_for_active_adminの両方が実行されてしまいます。 もしset_sentry_contextに他にもユーザー向けに実行するロジックがあるとすれば、副作用が起きる可能性があります。 そもそもset_sentry_contextはActiveAdminでは不要なので、skip_before_actionで不要な処理を実行しないように設定します。

ActiveAdmin.setup do |config|
  config.before_action = :set_sentry_context_for_active_admin
  config.skip_before_action = :set_sentry_context
end