diff --git a/docs/scoping.md b/docs/scoping.md index 30677c77..8f195b58 100644 --- a/docs/scoping.md +++ b/docs/scoping.md @@ -37,6 +37,29 @@ class PostPolicy < ApplicationPolicy end ``` +Or just pass class name where included logic of scope to `relation_scope` and write `.call` method: + +```ruby +class PostsController < ApplicationController + def index + @posts = authorized_scope(Post.all) + end +end + +class PostPolicy < ApplicationPolicy + relation_scope AuthorizedPosts +end + +class AuthorizedPosts + class << self + def call(policy, relation) + user = policy.user + user.admin? ? relation : relation.where(user: user) + end + end +end +``` + ## Define scopes To define scope you should use either `scope_for` or `smth_scope` methods in your policy: diff --git a/lib/action_policy/policy/scoping.rb b/lib/action_policy/policy/scoping.rb index cd83b0eb..df7aff63 100644 --- a/lib/action_policy/policy/scoping.rb +++ b/lib/action_policy/policy/scoping.rb @@ -113,8 +113,11 @@ def lookup_type_from_target(target) module ClassMethods # :nodoc: # Register a new scoping method for the `type` - def scope_for(type, name = :default, &block) + def scope_for(type, name = :default, callable = nil, &block) + name, callable = prepare_args(name, callable) + mid = :"__scoping__#{type}__#{name}" + block = ->(target) { callable.call(self, target) } if callable define_method(mid, &block) @@ -154,6 +157,14 @@ def scope_matchers [] end end + + private + + def prepare_args(name, callable) + return [name, callable] if name && callable + return [name, nil] if name.is_a?(Symbol) || name.is_a?(String) + [:default, name] + end end end end diff --git a/test/action_policy/rails/scope_matchers_test.rb b/test/action_policy/rails/scope_matchers_test.rb index 4130b4a1..5a5f20a0 100644 --- a/test/action_policy/rails/scope_matchers_test.rb +++ b/test/action_policy/rails/scope_matchers_test.rb @@ -154,6 +154,96 @@ def test_named_filtered_params_admin end end +class TestRailsScopeMatchersViaClass < ActionController::TestCase + class AuthorizedPosts + class << self + def call(policy, relation) + user = policy.user + scope = user.admin? ? relation : relation.where(user: user) + scope.order(:title) + end + end + end + + class PostPolicy < ActionPolicy::Base + relation_scope AuthorizedPosts + end + + class PostsController < ActionController::Base + authorize :user, through: :current_user + + def index + render json: authorized_scope(AR::Post.all) + end + + private + + def current_user + @current_user ||= AR::User.find(params[:user_id]) + end + end + + tests PostsController + + attr_reader :admin, :guest + + def setup + ActiveRecord::Base.connection.begin_transaction(joinable: false) + @guest = AR::User.create!(name: "Jack") + @admin = AR::User.create!(name: "John", role: "admin") + end + + def teardown + ActiveRecord::Base.connection.rollback_transaction + end + + def json_body + @json_body ||= JSON.parse(response.body) + end + + def test_authorized_relation_guest + get :index, params: {user_id: guest.id} + + assert_equal 0, json_body.size + end + + def test_authorized_relation_admin + get :index, params: {user_id: admin.id} + + assert_equal 0, json_body.size + end + + def test_authorized_association + AR::Post.create!(title: "[wip]", user: admin) + AR::Post.create!(title: "Good news!", user: guest) + + get :index, params: {user_id: guest.id} + + assert_equal 1, json_body.size + assert_equal "Good news!", json_body.first["title"] + end + + def test_authorized_association_2 + AR::Post.create!(title: "[wip]", user: admin) + AR::Post.create!(title: "Good news!", user: guest) + + get :index, params: {user_id: admin.id} + + assert_equal 2, json_body.size + assert_equal ["Good news!", "[wip]"], json_body.map { _1["title"] } + end + + def test_authorized_association_3 + AR::Post.create!(title: "[wip]", user: guest) + AR::Post.create!(title: "Admin news", user: admin) + AR::Post.create!(title: "Good news!", user: guest) + + get :index, params: {user_id: guest.id} + + assert_equal 2, json_body.size + end +end + # See https://github.com/palkan/action_policy/issues/101 class TestRelationMutability < ActionController::TestCase class UserPolicy < ActionPolicy::Base