LoginSignup
6
1

More than 5 years have passed since last update.

Punditのscopeでネストしたリソースに対応する方法

Last updated at Posted at 2018-10-02

Punditでネストしたリソースに対してscopeをかけたい時の話。
別の言い方をすると
PunditでURLに紐付いたリソースを使ってscopeしたい時の話。

TL;DR

PunditのuserをUserContextクラスでラップして、与えたいものを先に入れてあげよう。

環境例

routes.rb

routes.rb
resources :users
resources :magazines do
  resources :ads
end

user.rb

user.rb
has_many :magazines

magazine.rb

magazine.rb
belongs_to :user
has_many :ads

ad.rb

ad.rb
belongs_to :magazine

さあ、indexアクションでmagazineに紐付いてるad一覧を表示するぞ

magazines/:magazine_id/ad にアクセスした際に、URLで指定されたmagazineに紐付いているadだけが欲しい時に

application_controller.rb

application_controller.rb
include Pundit

ads_controller.rb

ads_controller.rb
class AdsController < ApplicationController
  def index
    authorize Ad
    @ads = policy_scope(Ad)
  end

ad_policy.rb

ad_policy.rb
class AdPolicy < ApplicationPolicy
  attr_reader :user, :record

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

  def index?
    true
  end

  class Scope
    attr_reader :user, :scope

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

    def resolve
      scope.where(magazine_id: 〇〇) ←あれ?どう書くの?
    end
  end
end

あれ? これは問題ですね…

  • user.magazines…? 指定できないなぁ。
  • @magazine…? 持ってこれないなぁ。
  • 引数…? Pundit公式 「Pundit does not allow you to pass additional arguments to policies for precisely this reason.

URLの:magazine_idを渡したいだけなんだけど…あれれ?

こーすれば、万事解決なのだ

PunditのuserをUserContextクラスでラップして、与えたいものを先に入れてあげよう!

ads_controller.rb

ads_controller.rb
class AdsController < ApplicationController
  before_action :set_magazine #変更点

  class UserContext #変更点
    attr_reader :user, :magazine

    def initialize(current_user, magazine)
      @user = current_user #devise
      @magazine = magazine
    end
  end

  def pundit_user #変更点
    UserContext.new(current_user, @magazine)
  end

  def index
    authorize Ad
    @ads = policy_scope(Ad)
  end

  private
    def set_magazine #変更点
      @magazine = Magazine.find(params[:magazine_id])
    end

ad_policy.rb

ad_policy.rb
class AdPolicy < ApplicationPolicy
  attr_reader :user, :record

  def initialize(user, record)
    @user = user.user #変更点
    @record = record
  end

  def index?
    true
  end

  class Scope
    attr_reader :user, :scope

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

    def resolve
      scope.where(magazine_id: user.magazine.id) #変更点
    end
  end
end

こうすることで、Policyの中で見れるuser(名前が嫌なら各自で別のに変えてあげてください)はUserContextクラスのインスタンスとなり、そのインスタンスメソッドでuserとmagazineを呼べるようになります。
user.user → Userクラスのインスタンス
user.magazine → Magazineクラスのインスタンス
これで、指定したマガジンの広告だけを拾ってindexに表示させることができました!
めでたしめでたし

参考

あとがき

Readme読んで、IPアドレスの時にしかできないかと思って30分無駄にしたのは内緒

これを書いている場所は株式会社mofmof の社内です。
アジャイルなチームをレンタルしてくれる、goodな会社です。レンタルとは言うものの、客先常駐はせずにmofmof社内に常駐するスタイルを貫いているため、社員が皆仲良い!寂しくない!
人も募集中なそうなので、気になる方は応募してみては。

6
1
0

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
6
1