varvet / pundit

Minimal authorization through OO design and pure Ruby classes
MIT License
8.24k stars 627 forks source link

Question: scopes on User object for users matching a policy #693

Closed awh-tokyo closed 2 years ago

awh-tokyo commented 2 years ago

Short version of question: How to make scopes on the User object so I can get a collection of Users who have access to a feature, while keeping all the policy stuff in the same place?

More information:

My application has many user classes, only some of whom can be assigned to tickets:

class FeaturesPolicy < Struct.new(:user, :features)
  def dispatch?
    user.has_any_role?(:tenant_internal_field_tech, :tenant_external_field_tech)
  end

This works as expected:

- if policy(:features).dispatch?
  = active_link_to(my_visits_url, {:class => 'sidebar-link', :wrap_tag => :li, :wrap_class => 'sidebar-item', :active => :exclusive}) do
    i.align-middle data-feather="user-check"
    span.align-middle My Scheduled Visits
    span.sidebar-badge.badge.badge-primary = Visit.for_user(current_user).active.count

However, the user does not dispatch himself; an administrator does the dispatches. I would like a collection of User objects who can be dispatched to populate dropdown lists, for example. Currently I have the following, which of course works, but makes way more SQL queries than is necessary:

= f.association :user, :collection => User.ordered.select { |u| FeaturesPolicy.new(u, :features).dispatch? }, :label_method => :full_name

The solution that I don't want is to define a scope on the User model (User.can_dispatch.ordered) because then I'd have to change in two places if I wanted different classes of users to be able to have access to be dispatched.

All of the example scopes in the documentation are about returning records that one particular user has access to. In my case, I would like to return User records that have access to a feature. What is the commonly-accepted pattern for this?

Thank you in advance for any suggestions!

dgmstuart commented 2 years ago

Unfortunately this is not possible in the general case: the challenge is that methods in policies can contain arbitrary logic involving both the user and the record, and there's no general way to convert arbitrary logic into an ActiveRecord scope which does the same thing in the database.

I feel like you're not alone in raising this question, but we don't recommend any particular approach. See issue #368 for a related discussion.