Punditを使ってユーザーの権限を管理する
はじめに
Pundit
というGemを使ってユーザーの権限を管理する方法について。
前提
ユーザーの種類は、管理者(admin)、編集者(editor)、ライター(writer)の3種類があります。
そして、記事(article)を作成、更新、削除する際には、管理者か編集者でないとできないようにします。
設定方法
まずはGemをインストールします。
gem 'pundit'
続いてapplicationコントローラにインクルードします。
class ApplicationController < ActionController::Base include Pundit end
punditをインストールします。
$ rails g pundit:install
するとapp/policies/
配下にapplication_policy.rb
が生成されます。
application_policy.rb
に下記設定を記述します。
class ApplicationPolicy attr_reader :user, :record def initialize(user, record) @user = user @record = record end def scope Pundit.policy_scope!(user, record.class) end class Scope attr_reader :user, :scope def initialize(user, scope) @user = user @scope = scope end def resolve scope end end end
第一引数のuserには、current_userが入ります。
第二引数のscopeには、権限をチェックしたいオブジェクトが入ります。
続いて、上記のクラスを継承したarticle_policy.rb
の設定をします。
class ArticlePolicy < ApplicationPolicy def index? true end def show? true end def create? user.admin? || user.editor? end def edit? true end def update? user.admin? || user.editor? end def destroy? user.admin? || user.editor? end class Scope < Scope def resolve scope end end end
上記のように権限をチェックしたいメソッドに「?」をつけることで定義できます。
def create? user.admin? || user.editor? end # createアクションで、userが管理者か編集者の場合は処理を実行できる。
コントローラ側では、第一引数にArticle
や@article
のように対象のオブジェクトを指定してauthorizeメソッドを呼び出します。
class Admin::ArticlesController < ApplicationController layout 'admin' before_action :set_article, only: %i[edit update destroy] def index authorize(Article) @search_articles_form = SearchArticlesForm.new(search_params) @articles = @search_articles_form.search.order(id: :desc).page(params[:page]).per(25) end def new @article = Article.new end def create authorize(Article) @article = Article.new(article_params) @article.state = :draft if @article.save redirect_to edit_admin_article_path(@article.uuid) else render :new end end def edit authorize(@article) end def update authorize(@article) @article.assign_attributes(article_params) @article.adjust_state if @article.save flash[:notice] = '更新しました' redirect_to edit_admin_article_path(@article.uuid) else render :edit end end def destroy authorize(@article) @article.destroy redirect_to admin_articles_path end private def article_params params.require(:article).permit( :title, :description, :slug, :state, :published_at, :eye_catch, :category_id, :author_id, :eyecatch_width, :eyecatch_location, tag_ids: [] ) end def search_params params[:q]&.permit(:title, :category_id, :author_id, :body, :tag_id) end def set_article @article = Article.find_by!(uuid: params[:uuid]) end end
最後に、権限がないユーザーが該当ページに訪れたときのテンプレートの作成と設定をします。
module Blog class Application < Rails::Application config.action_dispatch.rescue_responses["Pundit::NotAuthorizedError"] = :forbidden end end
<!DOCTYPE html> <html> <head> <title>Access to this page is forbidden.</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <style> body { background-color: #EFEFEF; color: #2E2F30; text-align: center; font-family: arial, sans-serif; margin: 0; } div.dialog { width: 95%; max-width: 33em; margin: 4em auto 0; } div.dialog > div { border: 1px solid #CCC; border-right-color: #999; border-left-color: #999; border-bottom-color: #BBB; border-top: #B00100 solid 4px; border-top-left-radius: 9px; border-top-right-radius: 9px; background-color: white; padding: 7px 12% 0; box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); } h1 { font-size: 100%; color: #730E15; line-height: 1.5em; } div.dialog > p { margin: 0 0 1em; padding: 1em; background-color: #F7F7F7; border: 1px solid #CCC; border-right-color: #999; border-left-color: #999; border-bottom-color: #999; border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; border-top-color: #DADADA; color: #666; box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); } </style> </head> <body> <!-- This file lives in public/404.html --> <div class="dialog"> <div> <h1>Access to this page is forbidden.</h1> <p>You are not authorized to access this page.</p> </div> <p>If you are the application owner check the logs for more information.</p> </div> </body> </html>
開発環境でもこのエラーページが表示されるようにするには下記設定を変更します。
Rails.application.configure do config.consider_all_requests_local = false # falseにする。デフォルトではtrue end
これで権限のないユーザーが該当ページに訪れると下記のようになります。