Rails
activeadmin

cancanを使わずにactive_adminの管理画面で、current_userの権限によるviewの出し分けをする

はじめに

active adminを使用して、管理画面を作った際にcurrent_userの権限に応じてviewを出し分ける。
という一見簡単そうな要件を実装するのに思いがけず時間を取られた(調査に時間かかったorz)ので、備忘録も兼ねてまとめておきたいと思います。

そもそも、権限つけるならcancancan使えば良いんじゃないの?

cancancanを使えば、viewの出し分けは簡単に実装できるのですが、今回自分がやりたい要件にはcancancanはtoo muchなのと、管理画面のためのmodelが増えるのは嫌だったので、cancancanは使わずに実装することになりました。
(ほんとは管理アプリケーションと、サービスのアプリケーションは違うサーバーに実装したかった。。。)

準備

active adminの導入は、いろんな方がすでに書いているので省略させていただきます。僕が実装した時はこの記事を参考にしたと思います。
https://qiita.com/yutackall/items/2b16d1ac2bd64dedd16e

current_userの権限を見てviewの出し分けをするのが目的なので、User modelにroleカラムを追加します。
rails enumについてはこちらが参考になると思います。
https://qiita.com/shizuma/items/d133b18f8093df1e9b70

userはサービスを利用する一般ユーザー、administratorは管理者、staffは管理者よりカジュアルな権限を持つ人とします。
{ user: '一般ユーザー', administrator: 'マネージャー', staff: '社員' }とでも思って下さいw

app/model/user.rb
class User < ApplicationRecord
  #enum の設定をします
  enum role: [ :user, :administrator, :staff]
  ...
  ...
end

これでUserに権限のフラグをつけることができました。

サンプルコード

User modelの管理画面上でviewの出し分けを実装することにします。
サンプルコードは下記です。

app/admin/user.rb
ActiveAdmin.register User do

  actions :index, :show, :update, :edit

# ページング
  config.per_page = 20

# 検索条件
  filter :id
  filter :user_id
  filter :display_name
  filter :email

# ファーストビューのエレメント
  index do
    column :id
    column :user_id
    column :display_name
    column :mail_address

    actions
  end

# 詳細ページ
  show do
    attributes_table do
      row :id
      row :user_id
      row :display_name
      row :mail_address
      row :is_banned
      row :created_at
      row :updated_at
    end
  end

# 編集項目
  form do |f|
    inputs  do
      input :id,            input_html: { disabled: true } # これは編集できなくするオプションです
      input :user_id,                  
      input :display_name
      input :mail_address
      input :is_banned
    end
    actions
  end

  permit_params :user_id, :display_name, :mail_address, :is_banned

end

viewの出し分け

いつものようにこんな感じで書きたくなるのですが、エラーします。。。
どうやら少し違う書き方をしないといけないようです。

ActiveAdmin.register User do

if current_user.role == :admin
  filter :id
  filter :display_name
  filter :email
end
...
...

=> # app/admin/users.rb:2:in `block in <top (required)>': undefined local variable or method `current_user' for #<ActiveAdmin::ResourceDSL:0x00007feb5a3aea10> (NameError)

まずは検索条件のエレメントを、権限によって出し分けたいと思います。
administrator権限を持ってるユーザーだけが、emailによる絞りこみをできるようにした場合、以下のような書き方になります。

# 検索条件
  filter :id
  filter :user_id
  filter :display_name
  filter :email, if: proc { current_user.role == 'administrator' } # or { current_user.administrator? }

次ですが、index, show, edit(formの部分です)で使用するviewの切り替えは共通してます。
下記だとadministrator権限を持つユーザーでないと、showアクションのページに来ても何も表示しないことになっています。

show do
  current_user.role == 'administrator'
    attributes_table do
      row :id
      row :user_id
      row :display_name
      row :mail_address
      row :is_banned
      row :created_at
      row :updated_at
    end
  end
end

ストロングパラメータもこんな感じで分けることができます。

permit_params do
  if current_user.role == 'administrator'
    params = [ :user_id, :display_name, :mail_address, :is_banned]
  end
end

まとめ

active adminのgithubリポジトリで質問したところ、今はこのような書き方にしか対応していないとの回答が帰ってきたので、if文が増えてしまって嫌なのですが、このような書き方になりました。
ブロック内でしかcurrent_userメソッドを使えないなんてほんと不便だし、自分で作りたいと強く思いましたが、短時間で管理画面作れるメリットはやっぱりデカイのでactive admin はしばらく使うと思います。

個人的には、サービスリリース時はactive adminくらいの管理画面を使って、サービスが大きくなって管理画面でいろんなことやりたい。となったら自作するのがいいと思いました。