Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allows additional methods to be used with Scopes #443

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 16 additions & 10 deletions lib/pundit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,23 @@ 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.
#
# @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.
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
12 changes: 12 additions & 0 deletions spec/pundit_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ class Scope < Struct.new(:user, :scope)
def resolve
scope.published
end

def unpublished
scope.unpublished
end
end

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

def self.unpublished
:unpublished
end

def to_s
"Post"
end
Expand Down