Punditとは
READMEには、以下の記述がされています。
Pundit provides a set of helpers which guide you in leveraging regular Ruby classes and object oriented design patterns to build a simple, robust and scaleable authorization system.
Punditは、Rubyクラスやオブジェクト指向のデザインパターンを活用して、シンプルで堅牢でスケーラブルな認証システムを構築するためのヘルパーを提供しています。
とある通り、認可まわりの機能を作成するのに有効なGemのようです。
Punditを使う
では、使い方を見ていきます。
Punditのセットアップ
最初はgemのインストール
gem "pundit"
続いて、Punditをapplication controllerにinclude
class ApplicationController < ActionController::Base
include Pundit
protect_from_forgery
end
オプションで、ジェネレータを使用してapplication policyを生成することができます。
rails g pundit:install
create app/policies/application_policy.rb
Policiesについて
Punditはpolicyという概念に焦点を当てています。policyクラスをapp/policies/
に配置します。
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
Punditクラスは、以下のことを前提としています。
- クラスの最後にPolicyとつける
- 最初の引数は、userです。Punditはcurrent_userメソッドを呼び出して、この引数に送る
- 第二引数は認可をチェックしたいモデルオブジェクトです。ActiveRecordやActiveModelである必要はなく、なんでもよい
- Policyクラスはクエリメソッドを実装する。このメソッドはコントローラのアクションに割り当てられる。
Policyの使用
application policyを継承したり、独自の基本クラスを継承して継承したい場合があります。その場合は以下のようにします。
class PostPolicy < ApplicationPolicy
def update?
user.admin? or not record.published?
end
end
ApplicaitonPolicyでは、モデルオブジェクトをrecord
で表します。上記の例では、record
は、Postのモデルオブジェクトということになります。
コントローラ内にPostクラスのインスタンスがあれば、以下のようにできます。
def update
@post = Post.find(params[:id])
authorize @post
if @post.update(post_params)
redirect_to @post
else
render :edit
end
end
authorizeメソッド
は自動的にPostが該当するPostPolicyを確認し、Policyをインスタンス化しcurrent_user
とrecord
を渡します。
Policyを推論する際は、アクション名から行います。updateアクションならPolicyのupdate?メソッドになります。
上記の例では、以下のように推測しています。
unless PostPolicy.new(current_user, @post).update?
raise Pundit::NotAuthorizedError, "not allowed to update? this #{@post.inspect}"
end
コントローラのアクション名とPolicyのメソッド名が異なる場合は、authorizeメソッドの第二引数にメソッド名をシンボルで渡します。
下記の例では、publishアクションに対して、update?メソッドで認証を行います。
def publish
@post = Post.find(params[:id])
authorize @post, :update?
@post.publish!
redirect_to @post
end
authorizeメソッドの第一引数に渡すインスタンスがなければ、クラスを渡します。
Policy:
class PostPolicy < ApplicationPolicy
def admin_list?
user.admin?
end
end
Controller:
def admin_list
authorize Post
authorizeメソッドはオブジェクトを返すので、以下のようにつなげることができます。
def show
@user = authorize User.find(params[:id])
end
ビューとコントローラで`plicyメソッドを`を使用してPolicyのインスタンスを保持することができる。
<% if policy(@post).update? %>
<%= link_to "Edit post", edit_post_path(@post) %>
<% end %>
Policyに対応するモデルやクラスが無い場合は、シンボルを渡します。
Policy:
class DashboardPolicy < Struct.new(:user, :dashboard)
def show?
# ...
end
end
Controllers:
authorize :dashboard, :show?
Views:
<% if policy(:dashboard).show? %>
<%= link_to 'Dashboard', dashboard_path %>
<% end %>