diff --git a/README.md b/README.md index b05c1e0e..e7116e88 100644 --- a/README.md +++ b/README.md @@ -285,6 +285,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 559b8a65..fdcd646a 100644 --- a/lib/pundit.rb +++ b/lib/pundit.rb @@ -74,10 +74,11 @@ def authorize(user, record, query) # @see https://github.com/elabs/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. # @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 = PolicyFinder.new(scope).scope - policy_scope.new(user, scope).resolve if policy_scope + policy_scope.new(user, scope).public_send(method) if policy_scope end # Retrieves the policy scope for the given record. @@ -85,10 +86,11 @@ def policy_scope(user, scope) # @see https://github.com/elabs/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 # @return [Scope{#resolve}] instance of scope class which can resolve to a scope - def policy_scope!(user, scope) - PolicyFinder.new(scope).scope!.new(user, scope).resolve + def policy_scope!(user, scope, method = :resolve) + PolicyFinder.new(scope).scope!.new(user, scope).public_send(method) end # Retrieves the policy for the given record. @@ -116,8 +118,8 @@ def policy!(user, 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 @@ -209,10 +211,11 @@ def skip_policy_scope # # @see https://github.com/elabs/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. # @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope - def policy_scope(scope) + def policy_scope(scope, method = :resolve) @_pundit_policy_scoped = true - pundit_policy_scope(scope) + pundit_policy_scope(scope, method) end # Retrieves the policy for the given record. @@ -271,7 +274,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 0b752ad9..38d14439 100644 --- a/spec/pundit_spec.rb +++ b/spec/pundit_spec.rb @@ -43,6 +43,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 Comment end @@ -69,6 +73,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 Comment end @@ -411,6 +419,10 @@ expect(controller.policy_scope(Post)).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 c784c2f2..b572d9e1 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -30,6 +30,10 @@ class Scope < Struct.new(:user, :scope) def resolve scope.published end + + def unpublished + scope.unpublished + end end def update? @@ -62,6 +66,10 @@ def self.published :published end + def self.unpublished + :unpublished + end + def to_s "Post" end