Skip to content

Commit

Permalink
Copy varvet#443, allow define additional methods to the scope.
Browse files Browse the repository at this point in the history
  • Loading branch information
Eric-Guo committed Dec 19, 2022
1 parent dd53b69 commit de3ab93
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 10 deletions.
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,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
Expand Down
14 changes: 8 additions & 6 deletions lib/pundit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,10 @@ def authorize(user, possibly_namespaced_record, query, policy_class: nil, cache:
# @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

Expand All @@ -103,18 +104,19 @@ 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.
#
# @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

Expand All @@ -124,7 +126,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.
Expand Down Expand Up @@ -165,8 +167,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
end
12 changes: 8 additions & 4 deletions lib/pundit/authorization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,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.
Expand Down Expand Up @@ -161,8 +162,11 @@ 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
end
4 changes: 4 additions & 0 deletions spec/authorization_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@
expect(policy.post).to eq post
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(article) }.to raise_error(Pundit::NotDefinedError)
end
Expand Down
8 changes: 8 additions & 0 deletions spec/pundit_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,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
Expand Down Expand Up @@ -139,6 +143,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
Expand Down
8 changes: 8 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ class Scope < Struct.new(:user, :scope)
def resolve
scope.published
end

def unpublished
scope.unpublished
end
end

def update?
Expand Down Expand Up @@ -53,6 +57,10 @@ def self.published
:published
end

def self.unpublished
:unpublished
end

def self.read
:read
end
Expand Down

0 comments on commit de3ab93

Please sign in to comment.