Pundit

Punditについて

gem "pundit"

bundle install後以下入力

rails g pundit:install

これで、app/policies/配下にapplication_policy.rbというファイルが作成されます。 policies配下のファイルは「認可のルールを記述するファイル」と認識して差し支えないと思います。

class ApplicationPolicy
  attr_reader :user, :record

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

  def index?
    false
  end

  def show?
    false
  end

  def create?
    false
  end
end

このファイルで定義されるクラスApplicationPolicyを継承して、コントローラーごとの認可ルールを記述していきます。

Punditはデフォルトで current_user メソッドを呼んで user として自動的に引数に渡しています。

「Policy」という単語が接尾辞として付いているだけです。

taxonomy_policy.rb

class TaxonomyPolicy < ApplicationPolicy
  def index?
    true
  end

  def create?
    user.admin? || user.editor?
  end

  def update?
    true
  end

  def destroy?
    true
  end
end

このように書き方として

  • モデル名_policy.rbでファイルを作成
  • モデル名Policyでクラス名を定義
  • def アクション名?で認可ルール(policy)を記述

taxonomy_policy.rbを見てみましょう。createだけadminとeditorのみ権限があり、他は全ユーザーに権限があります。 generatorによって作成されたApplicationPolicyから継承するか、継承する独自の基本クラスを設定する必要があります。

仮にindex?にfalseを記載するとどうなるでしょう?category_controller、tag_controller、author_controllerのindexアクションは拒否されて、Pundit::NotAuthorizedErrorが投げられます。


category_controller

class Admin::CategoriesController < ApplicationController
  layout 'admin'

  before_action :set_categories, only: %i[index]
  before_action :set_category, only: %i[edit update destroy]

  def index
    authorize(Category)

    @category = Category.new
  end

  def create
    authorize(Category)

    @category = Category.new(category_params)

    if @category.save
      redirect_to admin_categories_path
    else
      set_categories
      render :index
    end
  end

 def edit
    authorize(@category)
  end

  def update
    authorize(@category)

    if @category.update(category_params)
      redirect_to admin_categories_path
    else
      render :edit
    end
  end
end

authorizeメソッドは、Taxonomyが一致するTaxonomyPolicyクラスを持っていることを自動的に推測し、このクラスをインスタンス化して、現在のユーザと指定されたレコードを渡します。



例外を403エラーにする

application_controller.rb

class ApplicationController < ActionController::Base
  include Pundit

  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

  private

  def user_not_authorized
    render file: Rails.root.join('public/403.html'), status: forbidden
  end

config/application.rb

以下を追加

config.action_dispatch.rescue_responses["Pundit::NotAuthorizedError"] = :forbidden

publicファイルに403エラーをの時に表示させるhtmlを作成

public/403.html

<!DOCTYPE html>
<html>
 <head>
  <title>Forbidden(403)</title>
  <meta name="viewport" content="width=device-width,initial-scale=1">
</head>

<body>
<p>このページへのアクセス権限がありません。</p>
</body>
</html>


参考URL

github.com