1

I am trying to figure out how to use pundit policy scopes in my article policy.

I have written an article policy, that nests a scope and then has a resolve method in it. The resolve method has alternatives based on who the current_user is.

My article policy has:

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

        # I now think I don't need these actions because I have changed the action in the articles controller to look for policy scope.
         # def index?
         #      article.state_machine.in_state?(:publish)
         # end

            def show?

            article.state_machine.in_state?(:publish) ||
            user == article.user ||
            article.state_machine.in_state?(:review) && user.org_approver ||
            false
        end
            end

            def create?
                article.user.has_role?(:author) 

            end

            def update?
                # user && user.article.exists?(article.id) #&& user.article.created_at < 15.minutes.ago
                user.present? && user == article.user 
                # add current state is not published or approved
            end

            def destroy?
                user.present? && user == article.user 
                # user.admin? 
                # user.present?
                # user && user.article.exists?(article.id)
            end



    end     

    private
        def article
            record
        end

        def resolve
            if user == article.user
                scope.where(user_id: user_id)
            elsif approval_required?
                scope.where(article.state_machine.in_state?(:review)).(user.has_role?(:org_approver))
            else
                article.state_machine.in_state?(:publish)    
            end 
        end


        def approval_required?

            true if article.user.has_role?(:author)
                 # elsif article.user.profile.organisation.onboarding.article_approval == true


            # if onboarding (currently in another branch) requires org approval
        end

        def org_approver
            if article.user.has_role? :author
                user.has_role? :editor
            # if onboarding (currently in another branch) requires org approval, then the approval manager for that org
            elsif article.user.has_role? :blogger
                user.has_role? :editor if user.profile.organisation.id == article.user.profile.organisation.id  
          end
        end

end

The example in the pundit docs shows how to use this for an index, but how do I use the resolve method for a show action? Can I write several resolve methods for the various other controller actions?

Pundit Scopes

Vasfed
  • 18,013
  • 10
  • 47
  • 53
Mel
  • 2,481
  • 26
  • 113
  • 273

1 Answers1

2

I dont have much experience with pundit, however by looking at documentation and your code the code I can see 2 things.

1 - You shouldnt use methods like show? inside your scope class.

inside your scope class, you should use only methods that returns a scope. the methods that returns boolean should be in the Policy level. But in your code I can boolean methods inside the scope class.

Instances of this class respond to the method resolve, which should return some kind of result which can be iterated over. For ActiveRecord classes, this would usually be an ActiveRecord::Relation.

from the docs

2 - Given that Scope are POROs (Plain Old Ruby Object) you can have more than one resolve methods (of course with a different name :)), because resolve is just a method name.

May be you can do something like

#policy
class ArticlePolicy < ApplicationPolicy
  attr_reader :user, :scope

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


  class Scope < Scope
    def resolve
      # some scope
    end

    def resolve_show
      #scope for show action 
      # E.g scope.all
    end
  end

  def show?
    article.state_machine.in_state?(:publish) ||
      user == article.user ||
      article.state_machine.in_state?(:review) && user.org_approver || false
  end
end

in your controller

#Articles controller
class ArticlesController < ApplicationController
  ...
  def show
    authorize Article 
    ArticlePolicy::Scope.new(current_user, Article).resolve_show
  end
  ...
end

This should first authorize your show method with ArticlePolicy#show? and the scope from ArticlePolicy::Scope#resolve_show

Disclaimer: Untested code, use at your own risk ;)

sameera207
  • 16,547
  • 19
  • 87
  • 152
  • Thanks so much @sameera207. This got me started on solving the problems. For others - the scopes need to be separated from the action controls. The scopes can be nuanced for the specifics of the data set. The action controls need to be capable of a true or false outcome (and if true, the scope is then applied). – Mel Jul 20 '16 at 05:15