Open7

adminを設けてアプリを管理する方法(Pundit + rails_admin)

Kohei ShimizuKohei Shimizu

当初の課題感

いま、Railsのアプリを開発しており、
admin権限を設けて、自分のアプリのテーマに沿わないユーザーをadmin権限で削除する機能を実装したいとおもったので、やり方も含めてメモしていきます!

Kohei ShimizuKohei Shimizu

まずはリサーチ

色々、調べて結果、やるべきことが2つあることが分かった。


1.admin権限を与える
2.管理画面系のUIを実装


まず、
1の方で、「あなたは、管理者か?、ただのユーザーか?」を問う記述を施して、
2の方で、「管理者なら、この管理画面をどうぞ」と特別なGUIの操作パネルを用意する感じ。

導入方法の記事で参考になりそうなものは下記。
・rails_adminを使用した管理画面の作成(cancancan + rails_admin)
https://qiita.com/jinta_02/items/d0ecd85468182598bbda
・Rails_adminで管理者機能作成(cancancan + rails_admin)
https://zenn.dev/goldsaya/articles/98145f56b79a05
・Ruby on Rails における権限Gem まとめ と GlobalState | Offers Tech Blog(1で使えるgemの比較記事)
https://zenn.dev/overflow_offers/articles/20221003-authority_ruby_gem

Kohei ShimizuKohei Shimizu

実装方針

リサーチを元に、1はpundit、2はrails_adminのgemを活用して実施することにしました。

理由は、

1は、cancancanの記事を観ていると、policyファイルの管理がファットになってしまって大変だったという意見があったため。puditは使いやすい慣れやすいという意見もあったので、開発機関から逆算して素早く実装できる方法で扱いやすい方法を選択しました。

2も同じ理由で、素早く実装できて扱いやすい方法として選択しました。

Kohei ShimizuKohei Shimizu

Punditの実装

まずは、公式READMEを読む。
https://github.com/varvet/pundit?tab=readme-ov-file

READMEに沿って、インストールします。

Gemfile
gem "pundit"
Teminal
budle install

インストールしたら、application_controllerに下記を追加

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  ・・・
  include Pundit::Authorization
  ・・・
end

このgemにはポリシーファイルのジェネレーターが実装されているので、実行します。

Teminal
rails g pundit:install

そうすると、policiesというフォルダと、中にapplication_policy.rbが生成されるはずです。ここに記述を施して、adminやユーザーに関する制限を加えていきます。

その前に、Userモデルにadminカラムを作成して、各ユーザーに管理者か一般ユーザーなのかを見分ける施しをしておきます。

Teminal
rails g migration AddAdminToUsers admin:boolean

デフォルトで、falseにしておきます。(一般ユーザーにしておく)

db/migrate/XXXXXXXXXXXXXX_add_admin_to_users.rb
class AddAdminToUsers < ActiveRecord::Migration[7.2]
  def change
    add_column :users, :admin, :boolean, default: false
  end
end
Teminal
rails db:migrate

ユーザーを1つだけ、admin権限をもたせましょう。私はid=1をadminにします。

Terminal
rails c
# adminを付与
user = User.first
user.update_attribute(:admin, true)
# 下記で確認して、admin: trueになっていればOK!
User.first
# 退出
exit
Kohei ShimizuKohei Shimizu

rails_admin

まずは、公式READMEを読む。インストール方法は、READMEのInstallationにある。(Getting Startedのどドキュメントをみてしまって時間取られた(一敗))
https://github.com/railsadminteam/rails_admin?tab=readme-ov-file

公式通りインストールします。
分からなかった時用の、日本語記事も貼っておきます。
https://zenn.dev/goldsaya/articles/98145f56b79a05#gemの導入(rails_admin)

Gemfile
gem "rails_admin", "~> 3.0"
Terminal
bundle install
# 終わったら公式のインストールコマンドを実行
rails g rails_admin:install

# こういうふうに聞かれると思いますが、adminの名前空間で良ければEnterを押すだけ
  ?  Where do you want to mount rails_admin? Press <enter> for [admin] > 

ここまで終わると、routes.rbに変化と、rails_admin.rbという新規作成ファイルが出現します。
まず、rails_admin.rbを修正します。(デフォルトでメジャーなgemはコメントを外すだけになっているので楽ですね!)

config/initializers/rails_admin.rb
RailsAdmin.config do |config|
  config.asset_source = :sprockets

  ### Popular gems integration

  ## == Devise == このコメントアウトを外しました。
  config.authenticate_with do
    warden.authenticate! scope: :user
  end
  config.current_user_method(&:current_user)

  ## == CancanCan ==
  # config.authorize_with :cancancan

  ## == Pundit == このコメントアウトを外しました。
  config.authorize_with :pundit

  ## == PaperTrail ==
  # config.audit_with :paper_trail, 'User', 'PaperTrail::Version' # PaperTrail >= 3.0.0

  ### More at https://github.com/railsadminteam/rails_admin/wiki/Base-configuration

  ## == Gravatar integration ==
  ## To disable Gravatar integration in Navigation Bar set to false
  # config.show_gravatar = true

  config.actions do
    dashboard                     # mandatory
    index                         # mandatory
    new
    export
    bulk_delete
    show
    edit
    delete
    show_in_app

    ## With an audit adapter, you can add:
    # history_index
    # history_show
  end
end

ここまで終わったら、localhost:3000/adminにアクセスしてみましょう。
下記の画面が出るはず。(出ない人は、サーバーやdockerの再起動を試してみてください)

punditで生成されたapplication_policy.rbに記述が必要です。

app/policies/application_policy.rb
# frozen_string_literal: true

class ApplicationPolicy
  attr_reader :user, :record

  def initialize(user, record)
    @user = user
    @record = record
  end

  # dashboardが許可されていなかったのでそれを渡してあげます。
  def dashboard?
    user.present? && user.admin?
  end

  def index?
    false
  end

  ・・・
end

これでダッシュボードが映るはずです!

Kohei ShimizuKohei Shimizu

例)管理者ダッシュボードでUserモデル一覧を表示する

出たのは良いものの、「無」の状態で何もできないので、例としてUserモデル一覧をダッシュボードで表示してみましょう。

正しい方法では無いと思うですが、自分なりのやり方です。
1.◯◯_policy.rbファイルを作成し、ApplicationPolicyを継承する。
2.localhost:3000/adminをページ更新。エラーと地道に格闘。

説明していきます。

1.◯◯_policy.rbファイルを作成し、ApplicationPolicyを継承する。

表題通り、Userモデル一覧を管理者ダッシュボードで表示させたいので、user_policy.rbを作成します。
application_policy.rbを複製して名前を変えると楽です。下記は複製したうえで少し修正したものです。

# frozen_string_literal: true

class UserPolicy < ApplicationPolicy

  def index?
    user.admin?
  end

  def show?
    user.admin? || user == record
  end

  def create?
    user.admin?
  end

  def new?
    create?
  end

  def update?
    user.admin? || user == record
  end

  def edit?
    update?
  end

  def destroy?
    user.admin? && user != record
  end

  class Scope < Scope
    def resolve
      if user.admin?
        scope.all
      else
        scope.none
      end
    end
  end
end

2.localhost:3000/adminをページ更新。エラーと地道に格闘。

これでページを更新するとエラーが出ます。大抵出るエラーが「◯◯?っていうメソッドが無いよ!」というものです。以下の写真は実際に出たエラー画面。

エラー画面に言われるままに、メソッドを追加します。
中身は、user.present? && user.admin?を書いていきます。(要はadminか?というのを確認する内容のメソッドにします)
例えば、上のエラー画面の解決はこんな感じ。

app/policies/user_policy.rb
# frozen_string_literal: true

class UserPolicy < ApplicationPolicy
・・・
  def export?
    user.present? && user.admin?
  end
・・・
end

ページをリロードすると、また「◯◯?メソッドが無いよ!」と言われるので、繰り返します。
そうすると、表示されます!