RailsでToDoサービスを作ろう(第4回)〜Punditで認可の権限管理する〜

にゅ~ぶる
にゅ~ぶる

こんにちは、にゅ〜ぶるです。

こんにちは〜、ぶるこだよ〜💕

ぶるこ
ぶるこ
にゅ~ぶる
にゅ~ぶる

さて、第4回目ですね!前回は、こちら
今日は、「Punditで認可の権限管理する」について、進めていきたいと思います!

にゅ~ぶる
にゅ~ぶる

Punditとは、認可の仕組みを提供してくれるライブラリ(Gem)です。
https://github.com/varvet/pundit

認可、つまり、ユーザが機能を使えるかどうかを判定する処理を行います。

にゅ~ぶる
にゅ~ぶる

では早速、使ってみましょう。
まずは、Gemfileに以下を追加します。

gem 'pundit'
にゅ~ぶる
にゅ~ぶる

そして、Gemfileを修正したら「bundle install」ですね。

bundle install
にゅ~ぶる
にゅ~ぶる

これでPunditが使えるようになりました。
次は、設定ファイルを作っていきます。
まずは、generatorが用意されているので、それを使います。

rails g pundit:install
にゅ~ぶる
にゅ~ぶる

そうすると、app/policies/配下にapplication_policy.rbというファイルが作成されます。

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

  def new?
    create?
  end

  def update?
    false
  end

  def edit?
    update?
  end

  def destroy?
    false
  end

  class Scope
    attr_reader:user, :scope

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

    def resolve
      scope.all
    end
  end

end

にゅ~ぶる
にゅ~ぶる

initializerで定義されるuserはデフォルトでcurrent_userが引数に割り当てられるようになっているので、deviseを使ってログイン機能を作った際はそのまま利用できます。
recordの方には対応するモデルを割り当てていきます。※後述

にゅ~ぶる
にゅ~ぶる

では、この設定を有効にするために、有効にしたいControllerに次の1行を追加してください。各Controllerに追加しても良いですが、ApplicationControllerに追加する事で、継承されている全てのControllerで有効にする事ができますので、こちらを使いましょう。

class ApplicationController < ActionController::Base
  include Pundit
end
にゅ~ぶる
にゅ~ぶる

次に、このファイルを継承して、各Controllerごとにファイルを作成していくことになります。

にゅ~ぶる
にゅ~ぶる

今回は、app/policies/配下にtask_policy.rbというファイルが作成しましょう。

class TaskPolicy < ApplicationPolicy
end
にゅ~ぶる
にゅ~ぶる

モデル名_policy.rbでファイルを作成
モデル名Policyでクラスを作成
def アクション名?で認可ルールを作成

と言う感じです。
AppicationPolicyを継承しているため、独自にしたいアクション名のメソッドのみ作成すれば良いですが、AppicationPolicyのアクションに対応したメソッドを見て貰うと分かる通り、全てがfalseになっているため、全てが使えない状態になりますので、追加していきましょう。※後からでも良いです。

にゅ~ぶる
にゅ~ぶる

そして次は、各ControllerのアクションでPunditを使うように設定していきます。
各アクションに以下の1行を追加してください。

にゅ~ぶる
にゅ~ぶる

モデルのインスタンスを利用して判断する場合は、
 authorize モデルのインスタンス
 ※このインスタンスがrecordに設定されます。
使わない場合は、
 authorize モデル
と書き方が少し違います。

def update
  authorize @task
  ・・ 中略 ・・
end

def index
  authorize Task
  ・・ 中略 ・・
end
にゅ~ぶる
にゅ~ぶる

では、設定したので、実際にブラウザでアクセスしてみましょう。
「rails s」で起動させたのち、「http://localhost:3000/tasks」にアクセスしてみてください。

エラーが出るよおおお😭

ぶるこ
ぶるこ
にゅ~ぶる
にゅ~ぶる

そうですね。
前述した通り、Deviseなどを使っている場合は、current_userが存在しますが、今回は省略させて貰っているので、作る必要がありますね。

にゅ~ぶる
にゅ~ぶる

固定で申し訳ありませんが、作っておきましょう…

class ApplicationController < ActionController::Base
  include Pundit
 
  def current_user
    # id=1のユーザを固定で返す
    User.find(1)
  end
end
にゅ~ぶる
にゅ~ぶる

ではもう一度アクセスしてみてください。

まだエラー!!💢

ぶるこ
ぶるこ
pundit_error
にゅ~ぶる
にゅ~ぶる

落ち着いて、ちゃんとみてね笑

あ!
これが認可されなかったエラーなのか!
えへへ💕

ぶるこ
ぶるこ
にゅ~ぶる
にゅ~ぶる

そうなんだ。エラー画面を作る必要があるけど、まずは通常のエラー画面が出ればOKです。

にゅ~ぶる
にゅ~ぶる

では、各アクションの認可の仕組みを追加してみてください。
一覧画面や新規画面については、誰でもOKなので、常にtrueを返すようにしましょう。
参照や更新、削除については、そのTaskを作成したユーザが自分の場合のにOK(true)とする必要がありますね。

にゅ~ぶる
にゅ~ぶる

答えは、こんな感じ。

class TaskPolicy < ApplicationPolicy
  def index?
    true
  end
 
  def show?
    @record.user == @user
  end
 
  def create?
    true
  end
 
  def new?
    create?
  end
 
  def update?
    @record.user == @user
  end
 
  def edit?
    update?
  end
 
  def destroy?
    @record.user == @user
  end
end
にゅ~ぶる
にゅ~ぶる

でも、これだけじゃダメですよ。
各アクションのメソッドで、Punditのauthorizeも必要ですね。

にゅ~ぶる
にゅ~ぶる

全てが完了したら、id=1のUserのTaskと、id=2のTaskを作るなどして、処理が正しく動いているか確認してみて下さい。

できたぁ💕

ぶるこ
ぶるこ
にゅ~ぶる
にゅ~ぶる

エラー画面については、rescue_fromを使ってごにょごにょしたり、固定のエラーページに飛ばしたり、強制でルートURLに遷移させたり、使い方は色々ありますので、自分にあった方法を見つけてみて下さい。

にゅ~ぶる
にゅ~ぶる

今回はここまでだよ。お疲れ様でした!
次回は、「一覧画面にRansack、kaminari」お楽しみにっ!!

最後まで読んでくれてありがとうございました!

現在、Railsのチュートリアル的な感じで、
「Todoサービスを作る!」をテーマにお送りしております。
アジェンダは、こちら

質問等ありましたら、コメントなりTwitterなりで頂ければ対応させて頂きますので、遠慮なく利用くださいね。