Rails 4 - Pundit - scoped policy for index

后端 未结 3 1213
萌比男神i
萌比男神i 2021-01-14 17:24

I am trying to learn how to use Pundit with my Rails 4 app.

I have the following models:

class User < ActiveRecord::Base
  has_one :profile
  has_         


        
相关标签:
3条回答
  • 2021-01-14 18:23

    I'm the previous commenter on that issue.

    For your EoiScope, you simply want what Eois the user has access to (because they belong to projects under this profile), independent from the project (this requirement is only for the controller, because is nested), so your controller should look something like this:

    Edit: Based on your latest attempt, I've updated the scope to account for Eois belonging directly to the user (not through a project) and you should simply scope it to a project or not based on the presence of params[:project_id], see updated answer.

    @eois = policy_scope(Eoi)
    @eois = @eios.where(project_id: params[:project_id]) if params[:project_id]
    

    And your scope should do joins until it reaches user or simply look for the user_id property on Eoi.

      class EoiPolicy < ApplicationPolicy
        class Scope < Scope
          def resolve
            scope.joins(project: : profile).where 'profiles.user_id = ? OR eois.user_id = ?', user.id, user.id
          end
        end
    
        # Other methods that differ from ApplicationPolicy's methods
      end
    

    Please note, Scope isn't calling eoi, but default* scope only knows about scope and user. * By default, I mean when it inherits from ApplicationPolicy::Scope

    0 讨论(0)
  • 2021-01-14 18:28

    In your first example, there's a couple of issues. Firstly, @eoi does not exist, and can't exist. The @eoi variable is set in the controller, and this is a different object. It doesn't work in the same way as your views where this is accessible, so this will never be set.

    Equally, the eoi variable will not be set, as your initialize method is only assigning the user and resource variables, so they're the only two you have access to (unless you rename)

    The scope in the policy works a little differently to how you think it works. The policy itself generally takes the user logged in, and a class, or a record that you are authorising. The scope however, doesn't normally take a record as the second argument. It is a scope, so either an active record sub-class, or a relation. You're not restricted to this however, and you could work around it by supplying a record but do note this is not normal behaviour for Pundit.

    In order to achieve what you're after, you should only have to make a few adjustments:

    class EoiPolicy < ApplicationPolicy
    
      class Scope
        attr_reader :user, :eoi
    
        def initialize(user, eoi)
          @user = user
          @eoi  = eoi
        end
    
        def resolve
          if user.profile.project.id == eoi.project_id
            Eoi.where(project_id: user.profile.project.id)
          elsif user.id == eoi.user_id
            Eoi.where(user_id: user.id)
          else
            nil
          end
        end
      end
    
      def index?
        user.profile.project.id == record.project_id or user.id == record.user_id
      end
    
      def new?
        true
      end
    
      def show?
        user.profile.project.id == record.project_id? or user.id == record.user_id
      end
    
      def edit?
        user.id == record.user.id
      end
    
      def create?
        true 
      end
    
      def update?
        user.id == record.user.id
      end
    
      def destroy?
        user.id == record.user.id
      end
    
    
    end
    

    The main changes here are that the attr_reader :user, :scope is now attr_reader :user, :eoi which will give you access to eoi within that scope.

    Access to this is no longer prefixed with @ as this is in-line with how pundit works.

    Throughout the rest of the policy, @eoi again cannot work, but this has been changed to record (assuming this is what it is in ApplicationPolicy). Please bear in mind the the Scope, and the rest of the policy are two different classes.

    With this setup, you should now be able to simply call policy_scope(@eoi) from within your controller. Note the usage of the @eoi variable here and NOT the Eoi class as before. This is crucial, as without this, you won't have access to things like user_id or project_id as those methods don't exist in the Eoi class, but only a record.

    I've also removed the ? symbols from the end of your if conditions. These are generally used to signify that the method being called returns a boolean, whereas you had them on the end of something that simply returns an integer. I'd imagine you'd actually get an error saying the method doesn't exist but if you've renamed things then you may want to put them back, but as I say that does go against ruby coding styles.

    And on a side-note, using or or and in statements instead of || or && can on the odd occasion behave differently to how you expect. In most scenarios it's fine, but it doesn't technically mean the same thing.

    Hope this all helps, let me know if you have any further issues with it.

    0 讨论(0)
  • 2021-01-14 18:28

    For others, I'm not sure if this is a solution that makes use of Pundit in the way it was intended, however it does generate the flows that I want, within the limits of my ability.

    Thank you to everyone who helped on this. I'm sure I've still got lots to learn about how to improve this, but for now, this is a solution that works.

    In summary - I now have two policies for 1 controller.

    Eoi Policy

    class EoiPolicy < ApplicationPolicy
    
      class Scope
    
        def initialize(user, scope)
          @user  = user
          @scope = scope
        end
    
        def resolve
          # selects all the EOI's for a given user
          @scope.where(user_id: @user.id)
        end
    
      end
    
      def index?
        true
      end
    

    Project Eoi Policy

    class ProjectEoiPolicy < ApplicationPolicy
      class Scope < Scope
        def resolve(project_id)
          project = Project.find(project_id)
          if project.owner?(@user)
            # if the user is the owner of the project, then get
            # all the eois
            project.eois
          else
            # select all the eois for the project
            # created by this user
            Eoi.for_user(@user.id).for_project(project_id)
          end
        end
      end
    
    end
    

    Eoi Controller index action

    class EoisController < ApplicationController
      before_action :get_project, except: [:index, :show]
      before_action :set_eoi, only: [:show, :edit, :update, :destroy]
    
    
      def index
        if params[:project_id]
          @eois = ProjectEoiPolicy::Scope.new(current_user, Eoi).resolve(params[:project_id])
        else
          @eois = policy_scope(Eoi)
        end
      end
    
    0 讨论(0)
提交回复
热议问题