Rails
Gem
pundit

Rails+Devise+Pundit 多対多実装の備忘録

初心者による、Railsの認可GemであるPunditの備忘録です。

導入

インストールします。

gem "pundit"

application_controllerにincludeしときます。

application_controller.rb
class ApplicationController < ActionController::Base
  include Pundit

end

Punditで使うファイルを生成します。

$ rails g pundit:install

これで app/policies/application_policy.rbができます。ほぼ準備完了です。

概要

Punditとはご存知の通り、リソースベースで認可の仕組みを提供します。

私は多対多の関係をうまく動作させるのに利用させていただきました。
以下、拙いですが勘弁してください。
ユーザは複数のプロジェクトに参加し、プロジェクトは複数のユーザに参加されるというのを中間テーブルを利用し実現します。

ユーザ 1-* プロジェクト参加ユーザ *-1 プロジェクト

のようなイメージです。

例えば、「プロジェクトの詳細ページはプロジェクトに参加しているユーザしか閲覧することができない」という要件があったとします。
きっとProjectコントローラのshowメソッドらへんでうまいことやれば良いのでしょうね。
Punditではこれをコントローラとポリシーファイルで制御します。

projects_controller.rb
class ProjectsController < ApplicationController

  def show
    @project = Project.find(params[:id])
    authorize @project
  end

end

project_policy.rb
class ProjectPolicy < ApplicationPolicy

  def show?
    !!@record.users.find_by(id: @user.id)
  end

end

上記コードの動作はこんな感じです。

  1. projects_controllerのshowが呼ばれる
  2. show内で引数@projectを渡してauthorizeメソッドを呼ぶ
  3. 自動的にproject_policyのshow?メソッドが呼ばれる
  4. 検証を行い、boolを返す
  5. falseだった場合、NotAuthorizedErrorが発生する

動作

autorizeメソッドはauthorize(user, record, query)と定義されていますが、上記コードでは@projectのみ渡しています。userには暗黙でcurrent_userが渡されているので渡さなくて大丈夫です。
queryには、policy内で呼びたいメソッドを指定する時のみシンボルを指定しますが、詳しくはgithubを参照してください。
これらはそれぞれ@user, @recordに入ります。

今回はprojectモデルに関連した認可を扱いたいので、application_policyを継承したproject_policyを作成しました。
ここに「どんな時そのコントローラのメソッド実行を許可するのか」ということを定義します。

project_policy.rb
class ProjectPolicy < ApplicationPolicy

  def show?
    !!@record.users.find_by(id: @user.id)
  end

end

あとは先述の通りです。project_controller#showのauthorize(@project)は自動的にproject_policyのshow?メソッドを呼びます。show?では、渡された@record(=@project)と@user(=current_user)を用いて検証を行います。
上記コードではcurrent_userが@projectに(中間テーブルを通して)ひもづくユーザかどうか検索し、存在すればtrueを返します。つまり、プロジェクト参加者かどうか検証しているわけです。

検証失敗時

もし参加者でないユーザがプロジェクトをみようとしたら、falseが返ります。
するとauthorizeはNotAuthorizedErrorを投げます。拾いましょう。

application_controller.rb
class ApplicationController < ActionController::Base
  include Pundit

  rescue_from NotAuthorizedError, with: :render_404

  def render_404(exception = nil)
    if exception
      logger.info "Rendering 404 with exception: #{exception.message}"
    end

    render file: "#{Rails.root}/public/404.html", status: 404, content_type: 'text/html'
  end

end

404に飛ぶようにしました。
(参考)
るびま http://magazine.rubyist.net/articles/0047/0047-IntroductionToPundit.html

おしまい

本当にざっくりとこんな感じです。書き間違いとか追記したいこととかあったらまた書きます。
参考URLもあとで載せます。