Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use pundit scopes?

Tags:

I have just made the switch to Pundit from CanCan. I am unsure about a couple of things, and how Pundit is best used.

For example:

If you have a resource that can have multiple parent objects, for instance lets say a Goal belongs to a student and instructor. Therefor, a student can have many goals and an instructor can have many goals. In a controller index action you might do:

if params[:student_id].present?
  @account = Student.find(params[:student_id])
  @goals = @account.goals
elsif params[:instructor_id].present?
  @account Instructor.find(params[:instructor_id])
  @goals = @account.goals
end

params are not usable inside policies, so the logic needs to be done here. I think. For what I can tell, if you skip the policy_scope you will get an unauthorized error when viewing the index page for goals.

Would you:

@goals = policy_scope(@account.goals)

OR

@goals = policy_scope(Goal.scoped).where( account_id: @account.id)

What happens when you throw a bunch of includes in the mix?

  @example = policy_scoped(@school.courses.includes(:account => :user, :teacher ))

Or when needed to order...is this correct?

 policy_scope(Issue.scoped).order("created_at desc")

When using scopes: What is :scope here? Is :scope an instance of the model being evaluated? I've tried accessing its attributes via :scope, but didn't work.

  class Scope < Struct.new(:user, :scope)
like image 776
hellion Avatar asked Jan 31 '14 00:01

hellion


People also ask

Why use Pundit?

Pundit helps us to define policies which are PORC - Plain Old Ruby Classes - which means that the class does not inherit from other classes nor include in other modules from the framework. Thus makes it very easy to understand the code.

What is Pundit in Ruby?

Pundit is a Ruby gem that handles authorization via a very simple API. Remember that authorization is different from authentication — authentication is verifying that you are who you say you are, and authorization is verifying that you have permission to perform an action.


1 Answers

Reading through this from a security perspective I can see a couple things that bear mentioning. For example, if you are allowing users to specify the student_id and instructor_id param fields, what's to stop them from passing in an ID for someone other than themselves? You don't ever want to let a user specify who they are, especially when you are basing policies on the users type.

For starters, I would implement Devise and add an additional boolean field called instructor that would be true when the user was an instructor but default to false for students.

Then your Users would automatically have an instructor? method defined, which will return true if the value in the instructor column is true.

You could then add a helper for students:

def student?
  !instructor?
end

Now using Devise (which gives us access to a current_user variable) we can do things like current_user.instructor? which will return true if they are an instructor.

Now on to the policy itself. I just started using Pundit a few weeks ago, but this is what I'd do in your situation:

class GoalPolicy < ApplicationPolicy
  class Scope < GoalPolicy
    attr_reader :user, :scope

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

    def resolve
      @scope.where(user: @user)
    end
  end
end

Then your (I'm assuming GoalsController class and index method) method can look like:

def index
  policy_scope(Goal) # To answer your question, Goal is the scope
end

If you wanted to order you could also do

def index
  policy_scope(Goal).order(:created_at)
end

I just realized that you asked this question half a year ago, but hey! Maybe it'll answer some questions other people have and maybe I'll get some feedback on my own budding Pundit skills.

like image 76
Saul Avatar answered Sep 16 '22 19:09

Saul