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 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.
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