From e2288c25af565838020c0d4364c392dd0fc380e0 Mon Sep 17 00:00:00 2001 From: Eric-Guo Date: Wed, 29 Jul 2020 11:09:36 +0800 Subject: [PATCH] Copy #443, allow define additional methods to the scope. --- README.md | 39 +++++++++++++++++++++++++++++++++++++++ lib/pundit.rb | 26 ++++++++++++++++---------- spec/pundit_spec.rb | 12 ++++++++++++ spec/spec_helper.rb | 8 ++++++++ 4 files changed, 75 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 439928d3..9d7904cb 100644 --- a/README.md +++ b/README.md @@ -320,6 +320,45 @@ You can, and are encouraged to, use this method in views: <% end %> ``` +It is also possible to define additional methods the scope that you can use in +different situations. We'll add an `unpublished` scope to the +`PostPolicy::Scope`: + +``` ruby +class PostPolicy < ApplicationPolicy + class Scope < Scope + def resolve + if user.admin? + scope.all + else + scope.where(published: true) + end + end + + def unpublished + if user.admin? + scope.all + else + scope.where(published: false) + end + end + end + + def update? + user.admin? or not post.published? + end +end +``` + +To use the `unpublished` scope, simply pass the name of the method as the 2nd +argument to `policy_scope`: + +``` ruby +def index + @posts = policy_scope(Post, :unpublished) +end +``` + ## Ensuring policies and scopes are used When you are developing an application with Pundit it can be easy to forget to diff --git a/lib/pundit.rb b/lib/pundit.rb index 0b3e31a7..302d0eab 100644 --- a/lib/pundit.rb +++ b/lib/pundit.rb @@ -80,9 +80,10 @@ def authorize(user, record, query, policy_class: nil) # @see https://github.com/varvet/pundit#scopes # @param user [Object] the user that initiated the action # @param scope [Object] the object we're retrieving the policy scope for + # @param method [Symbol] the method to call on the scope. Defaults to :resolve. # @raise [InvalidConstructorError] if the policy constructor called incorrectly # @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope - def policy_scope(user, scope) + def policy_scope(user, scope, method = :resolve) policy_scope_class = PolicyFinder.new(scope).scope return unless policy_scope_class @@ -92,7 +93,7 @@ def policy_scope(user, scope) raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called" end - policy_scope.resolve + policy_scope.public_send(method) end # Retrieves the policy scope for the given record. @@ -100,10 +101,11 @@ def policy_scope(user, scope) # @see https://github.com/varvet/pundit#scopes # @param user [Object] the user that initiated the action # @param scope [Object] the object we're retrieving the policy scope for + # @param method [Symbol] the method to call on the scope. Defaults to :resolve. # @raise [NotDefinedError] if the policy scope cannot be found # @raise [InvalidConstructorError] if the policy constructor called incorrectly # @return [Scope{#resolve}] instance of scope class which can resolve to a scope - def policy_scope!(user, scope) + def policy_scope!(user, scope, method = :resolve) policy_scope_class = PolicyFinder.new(scope).scope! return unless policy_scope_class @@ -113,7 +115,7 @@ def policy_scope!(user, scope) raise InvalidConstructorError, "Invalid #<#{policy_scope_class}> constructor is called" end - policy_scope.resolve + policy_scope.public_send(method) end # Retrieves the policy for the given record. @@ -154,8 +156,8 @@ def pundit_model(record) # @api private module Helper - def policy_scope(scope) - pundit_policy_scope(scope) + def policy_scope(scope, method = :resolve) + pundit_policy_scope(scope, method) end end @@ -246,11 +248,12 @@ def skip_policy_scope # # @see https://github.com/varvet/pundit#scopes # @param scope [Object] the object we're retrieving the policy scope for + # @param method [Symbol] the method to call on the scope. Defaults to :resolve. # @param policy_scope_class [Class] the policy scope class we want to force use of # @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope - def policy_scope(scope, policy_scope_class: nil) + def policy_scope(scope, method = :resolve, policy_scope_class: nil) @_pundit_policy_scoped = true - policy_scope_class ? policy_scope_class.new(pundit_user, scope).resolve : pundit_policy_scope(scope) + policy_scope_class ? policy_scope_class.new(pundit_user, scope).public_send(method) : pundit_policy_scope(scope, method) end # Retrieves the policy for the given record. @@ -320,7 +323,10 @@ def pundit_user private - def pundit_policy_scope(scope) - policy_scopes[scope] ||= Pundit.policy_scope!(pundit_user, scope) + def pundit_policy_scope(scope, method = :resolve) + method = method.to_sym + key = method == :resolve ? scope : [scope, method] + + policy_scopes[key] ||= Pundit.policy_scope!(pundit_user, scope, method) end end diff --git a/spec/pundit_spec.rb b/spec/pundit_spec.rb index a380c1ef..0200a2c8 100644 --- a/spec/pundit_spec.rb +++ b/spec/pundit_spec.rb @@ -78,6 +78,10 @@ expect(Pundit.policy_scope(user, Post)).to eq :published end + it "returns an instantiated policy scope given the method to call on the scope" do + expect(Pundit.policy_scope(user, Post, :unpublished)).to eq :unpublished + end + it "returns an instantiated policy scope given an active model class" do expect(Pundit.policy_scope(user, Comment)).to eq CommentScope.new(Comment) end @@ -124,6 +128,10 @@ expect(Pundit.policy_scope!(user, Post)).to eq :published end + it "returns an instantiated policy scope given the method to call on the scope" do + expect(Pundit.policy_scope!(user, Post, :unpublished)).to eq :unpublished + end + it "returns an instantiated policy scope given an active model class" do expect(Pundit.policy_scope!(user, Comment)).to eq CommentScope.new(Comment) end @@ -538,6 +546,10 @@ expect(controller.policy_scope(Post, policy_scope_class: PublicationPolicy::Scope)).to eq :published end + it "returns an instantiated policy scope when a scope method is provided" do + expect(controller.policy_scope(Post, :unpublished)).to eq :unpublished + end + it "throws an exception if the given policy can't be found" do expect { controller.policy_scope(Article) }.to raise_error(Pundit::NotDefinedError) end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4197d548..02b69646 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -21,6 +21,10 @@ class Scope < Struct.new(:user, :scope) def resolve scope.published end + + def unpublished + scope.unpublished + end end def update? @@ -53,6 +57,10 @@ def self.published :published end + def self.unpublished + :unpublished + end + def self.read :read end