Punditでネストしたリソースに対してscopeをかけたい時の話。
別の言い方をすると
PunditでURLに紐付いたリソースを使ってscopeしたい時の話。
TL;DR
PunditのuserをUserContextクラスでラップして、与えたいものを先に入れてあげよう。
環境例
routes.rb
resources :users
resources :magazines do
resources :ads
end
user.rb
has_many :magazines
magazine.rb
belongs_to :user
has_many :ads
ad.rb
belongs_to :magazine
さあ、indexアクションでmagazineに紐付いてるad一覧を表示するぞ
magazines/:magazine_id/ad
にアクセスした際に、URLで指定されたmagazineに紐付いているadだけが欲しい時に
application_controller.rb
include Pundit
ads_controller.rb
class AdsController < ApplicationController
def index
authorize Ad
@ads = policy_scope(Ad)
end
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
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
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社内に常駐するスタイルを貫いているため、社員が皆仲良い!寂しくない!
人も募集中なそうなので、気になる方は応募してみては。