Search code examples
ruby-on-railsscopenestedpundit

Scope for nested resources using pundit resolve method


I am referring to my own question Rails Nested Resources with Pundit Allowing Index and finally came up with a working solution but is there not any much better solution defining scope.where(?) or scope.select(?) in the property_policy? How to get all the properties that only belongs to one specific deal using the pundit resolve method?

What I finally did :

properties_controller.rb

class PropertiesController < ApplicationController
before_action :set_deal, except: [:index, :all]
before_action :set_property, only: [:show, :edit, :update, :destroy]

def all
  @properties = Property.all
  authorize @properties
end

def index
  @deal = Deal.find(params[:deal_id])
  @properties = policy_scope(Deal)
end

def set_deal
  @deal = Deal.find(params[:deal_id])
  # pundit ######
  authorize @deal
  ###############
end
(...)
end

property_policy.rb

class PropertyPolicy < ApplicationPolicy
class Scope < Scope
def resolve
  scope.all if user.admin?
end
def all?
  user_is_admin?
end
def user_is_admin?
  user.try(:admin?)
end 
(...)
end

What I'd like better:

properties_controller.rb

def index
  @deal = Deal.find(params[:deal_id])
  @properties = policy_scope(Property) # => for # @properties = @deal.properties
  authorize @deal
end

and in the property_policy.rb something like

def resolve
  # scope.where(???) if user.admin? # only an admin user can see the @deal.properties
  # or any other solution using scope
 end

As a reminder 1 deal has many properties and 1 property belongs to one specific deal. My routes are nested deals/id/properties except for the full list of properties I have simple "/properties". Thanks a lot for helping.

** UPDATE **

I finally went for

properties_controller.rb

def index
  @deal = Deal.find(params[:deal_id])
  @properties = policy_scope(@deal.properties)
  authorize @properties, :index?
end

and in property_policy.rb

class PropertyPolicy < ApplicationPolicy
  class Scope < Scope
    def resolve
      user.admin? ? scope.all : scope.none
    end
  end
  def index?
    user_is_admin?
  end
  def user_is_admin?
    user.try(:admin?)
  end
end

Not sure if it is the proper way


Solution

  • What you want to do is pass a scope to the policy - not just a class.

    @properties = policy_scope(@deal.policies)
    
    class PropertiesPolicy
      class Scope < Scope
        def resolve
          user.admin? ? scope.all : scope.none
        end
      end
    end
    

    Another problem with your controller is that authorize @deal will call DealsPolicy#index? which is not what you want.

    To authorize an index action you want to call authorize with the model class (and not an instance):

    def index
      authorize Property # calls PropertiesPolicy#index?
      @deal = Deal.find(params[:deal_id])
      @properties = policy_scope(@deal.properties)
    end
    

    In that case you don't have to do anything special in your Scope#resolve method really. Just return scope since you can assume at that point that the user is an admin.