LoginSignup
57
42

More than 3 years have passed since last update.

Punditを使って権限を管理する

Last updated at Posted at 2017-12-02

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_userrecordを渡します。
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 %>
57
42
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
57
42