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


# [Pundit](https://github.com/varvet/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のインストール

```rb
gem "pundit"
```

続いて、Punditをapplication controllerにinclude

```rb
class ApplicationController < ActionController::Base
  include Pundit
  protect_from_forgery
end
```

オプションで、ジェネレータを使用してapplication policyを生成することができます。

```bash
rails g pundit:install

create  app/policies/application_policy.rb
```

## Policiesについて
Punditはpolicyという概念に焦点を当てています。policyクラスを`app/policies/`に配置します。

```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
```

Punditクラスは、以下のことを前提としています。

- クラスの最後にPolicyとつける
- 最初の引数は、userです。Punditはcurrent_userメソッドを呼び出して、この引数に送る
- 第二引数は認可をチェックしたいモデルオブジェクトです。ActiveRecordやActiveModelである必要はなく、なんでもよい
- Policyクラスはクエリメソッドを実装する。このメソッドはコントローラのアクションに割り当てられる。

### Policyの使用
application policyを継承したり、独自の基本クラスを継承して継承したい場合があります。その場合は以下のようにします。

```rb
class PostPolicy < ApplicationPolicy
  def update?
    user.admin? or not record.published?
  end
end
```

ApplicaitonPolicyでは、モデルオブジェクトを`record`で表します。上記の例では、`record`は、Postのモデルオブジェクトということになります。

コントローラ内にPostクラスのインスタンスがあれば、以下のようにできます。

```rb
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?メソッドになります。
上記の例では、以下のように推測しています。

```rb
unless PostPolicy.new(current_user, @post).update?
  raise Pundit::NotAuthorizedError, "not allowed to update? this #{@post.inspect}"
end
```

コントローラのアクション名とPolicyのメソッド名が異なる場合は、authorizeメソッドの第二引数にメソッド名をシンボルで渡します。
下記の例では、publishアクションに対して、update?メソッドで認証を行います。

```rb
def publish
  @post = Post.find(params[:id])
  authorize @post, :update?
  @post.publish!
  redirect_to @post
end
```

authorizeメソッドの第一引数に渡すインスタンスがなければ、クラスを渡します。

Policy:

```rb
class PostPolicy < ApplicationPolicy
  def admin_list?
    user.admin?
  end
end
```

Controller:

```rb
def admin_list
  authorize Post
```

authorizeメソッドはオブジェクトを返すので、以下のようにつなげることができます。

```rb
def show
  @user = authorize User.find(params[:id])
end
```

ビューとコントローラで`plicyメソッドを｀を使用してPolicyのインスタンスを保持することができる。

```erb
<% if policy(@post).update? %>
  <%= link_to "Edit post", edit_post_path(@post) %>
<% end %>
```

Policyに対応するモデルやクラスが無い場合は、シンボルを渡します。

Policy:

```rb
class DashboardPolicy < Struct.new(:user, :dashboard)
  def show?
    # ... 
  end
end
```

Controllers:

```rb
authorize :dashboard, :show?
```

Views:

```erb
<% if policy(:dashboard).show? %>
  <%= link_to 'Dashboard', dashboard_path %>
<% end %>
```