I'm struggling with authorization of an index action of a nested resource while using Pundit. Pundit is so slick and potentially easy, that I hate to have to cast it aside because I can't figure this out. I figure once I understand this part everything else will fall inline. I've read over a lot of people's posts asking very similar questions to the one I am asking and it seems the posts that come closest to the question I'm asking never get resolved. So, I'm still looking for an answer.
class User < ApplicationRecord
enum role: [:user, :admin]
after_initialization :set_default_role, :if => :new_record?
def set_default_role
self.role ||= :user
has_many :galleries, dependent: :destroy
end
class Gallery < ApplicationRecord
belongs_to :user
validates :user_id, presence: true
validates :title,, presence: true
end
I have user set up just how I want it with a UserPolicy. It doesn't inherit from the ApplicationPolicy because the tutorial I was looking at said I didn't need it.
my routes look like this:
devise_for :users, path: 'accounts
resources :users, shallow: true do
resources :galleries
end
Essentially I don't want one user to see another users stuff, i.e. index, show... actions. I believe this is called a closed system. My route for seeing a user's gallery(my nested resource) index looks like this:
localhost:/users/20/galleries
I don't want User 19 to ever see what User 20 has in their gallery index. How do I do that?
This is what my GalleryController looks like:
before_action :authenticate_user! #I'm using devise
def index
@galleries = current_user.galleries.all
#authorize ????? <<part of what I don't understand
end
private
def gallery_params
params.require(:gallery).permit(:title)
end
Really I don't know what I'm supposed to do in the GalleryPolicy for index. I have the show action worked out, because it's simply checking the instance of the gallery against the instance of user id. Here is the GalleryPolicy
attr_reader :user, :model
def initialize(user, model)
@user = user
@gallery = gallery
end
def show?
@gallery.user_id == user.id
end
def index?
# I can't figure it out
end
def new?
#actually this is confusing too
end
First of all, I have always written Pundit policies which inherit from ApplicationPolicy so let's start from there. ApplicationPolicy is initialized with a user and a record. The record will be your model that you want the user to be authorized to view. In this case, a gallery.
Secondly, every Policy should include a scope. This is also initialized inside of ApplicationPolicy but it is your job to restrict the scope as necessary.
class GalleryPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.where(:user_id => user.id)
end
end
# no need for an index? method here. we will see this in the controller
# essentially here we are checking that the user associated to the record
# is the same as the current user
def show?
record.user == user
end
# this is actually already the case in ApplicationPolicy but I added
# it here for clarity
def new?
create?
end
# who do we want to be able to create a gallery? anyone!
# so this simple returns true
def create?
true
end
end
Then in your GalleryController you will authorize the models each time. Only the index action is a bit different as we will see now:
# controllers/gallery_controller.rb
def index
@galleries = policy_scope(Gallery)
# as we specified the scope in GalleryPolicy to only include galleries which belong
# to the current user this is all we need here!
end
# your other actions will look something like this:
def show
@gallery = Gallery.find(params[:gallery_id]
authorize @gallery
end
def new
@gallery = Gallery.new # or @gallery = current_user.galleries.build
authorize @gallery
end
[...]
I hope this helps! Pundit is a really great tool once you get to grips with it.