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)
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.
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.
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 User
s 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With