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