Rails 7 – 用于 “has many through” 的 pundit 范围

huangapple go评论92阅读模式
英文:

Rails 7 - pundit scope for has many through

问题

I recently started using the Pundit gem for authorization in my Rails app. In my app I have models for Company, each Company can have multiple Employees, this is done through a has many through relationship:

company.rb

has_many :employees, dependent: :destroy
has_many :users, through: :employees

employee.rb

belongs_to :company

Employees can either be published or not published. In my view for listing company employees, I want to only display published employees for regular users, but other employees of a given company should see all employees for that company.

In my employees_controller#index I have a solution that currently looks like this:

  1. def index
  2. @employees = if current_user.is_employee?(@company)
  3. @company.employees.all
  4. else
  5. @company.employees.select { |employee| employee.published == true }
  6. end
  7. end

This works, but I'm trying to understand how to utilize Pundits scopes to achieve the same.

Pseudo code for a Pundit Scope (that's clearly not working, but gives an example of what I want to achieve):

EmployeePolicy

class Scope
def initialize(user, scope)
@user = user
@scope = scope
end

def resolve
if user.is_employee?(...) # can't get company from scope
scope.all
else
scope.where(published: true)
end
end
end

I've seen some examples of using joins to achieve this, but that seems a bit cumbersome, is there some other recommended way to achieve this using Pundit?

英文:

I recently started using the Pundit gem for authorization in my Rails app. In my app I have models for Company, each Company can have multiple Employees, this is done through a has many through relationship:

  1. # company.rb
  2. has_many :employees, dependent: :destroy
  3. has_many :users, through: :employees
  4. # employee.rb
  5. belongs_to :company
  6. # user ...

Employees can either be published or not published. In my view for listing company employees, I want to only display published employees for regular users, but other employees of a given company should see all employees for that company.

In my employees_controller#index I have a solution that currently looks like this:

  1. def index
  2. @employees = if current_user.is_employee?(@company)
  3. @company.employees.all
  4. else
  5. @company.employees.select { |employee| employee.published == true }
  6. end
  7. end

This works, but I'm trying to understand how to utilise Pundits scopes to achieve the same.

Pseudo code for a Pundit Scope (that's clearly not working, but gives an example of what I want to achieve):

  1. # EmployeePolicy
  2. class Scope
  3. def initialize(user, scope)
  4. @user = user
  5. @scope = scope
  6. end
  7. def resolve
  8. if user.is_employee?(...) # can't get company from scope
  9. scope.all
  10. else
  11. scope.where(published: true)
  12. end
  13. end
  14. end

I've seen some examples of using joins to achieve this, but that seems a bit cumbersome, is there some other recommended way to achieve this using Pundit?

答案1

得分: 1

  1. # app/models/user.rb
  2. class User < ApplicationRecord
  3. has_many :employees, dependent: :destroy
  4. has_many :companies, through: :employees
  5. end
  6. # app/models/employee.rb
  7. class Employee < ApplicationRecord
  8. belongs_to :company
  9. belongs_to :user
  10. end
  11. # app/models/company.rb
  12. class Company < ApplicationRecord
  13. has_many :employees, dependent: :destroy
  14. has_many :users, through: :employees
  15. end
  1. Company.create!(name: "one", users: [User.new, User.new])
  2. Company.create!(name: "two", users: [User.new, User.new])
  3. Employee.find(3).update(published: true)
  4. >> Employee.all.as_json
  5. => [{"id"=>1, "published"=>nil, "company_id"=>1, "user_id"=>1},
  6. {"id"=>2, "published"=>nil, "company_id"=>1, "user_id"=>2},
  7. {"id"=>3, "published"=>true, "company_id"=>2, "user_id"=>3},
  8. {"id"=>4, "published"=>nil, "company_id"=>2, "user_id"=>4}]
  1. # app/policies/employee_policy.rb
  2. class EmployeePolicy < ApplicationPolicy
  3. class Scope < Scope
  4. def resolve
  5. scope.where(company: user.company_ids).or(
  6. scope.where(published: true)
  7. )
  8. end
  9. end
  10. end

User #1 can see every employee from company #1 and only published from company #2:

  1. >> Pundit.policy_scope(User.first, Employee).as_json
  2. => [{"id"=>1, "published"=>nil, "company_id"=>1, "user_id"=>1},
  3. {"id"=>2, "published"=>nil, "company_id"=>1, "user_id"=>2},
  4. {"id"=>3, "published"=>true, "company_id"=>2, "user_id"=>3}]

User from company #2 can't see other company employees:

  1. >> Pundit.policy_scope(User.third, Employee).as_json
  2. => [{"id"=>3, "published"=>true, "company_id"=>2, "user_id"=>3},
  3. {"id"=>4, "published"=>nil, "company_id"=>2, "user_id"=>4}]

Scope that is not related to authorization should be applied in a controller:

  1. >> employee_scope = Pundit.policy_scope(User.first, Employee)
  2. >> employee_scope.where(company: Company.first).as_json
  3. => [{"id"=>1, "published"=>nil, "company_id"=>1, "user_id"=>1},
  4. {"id"=>2, "published"=>nil, "company_id"=>1, "user_id"=>2}]
  5. # user #1 authorization to see their own company employees didn't change
  6. # just currently looking at the other company
  7. >> employee_scope.where(company: Company.second).as_json
  8. => [{"id"=>3, "published"=>true, "company_id"=>2, "user_id"=>3}]
英文:
  1. # app/models/user.rb
  2. class User &lt; ApplicationRecord
  3. has_many :employees, dependent: :destroy
  4. has_many :companies, through: :employees
  5. end
  6. # app/models/employee.rb
  7. class Employee &lt; ApplicationRecord
  8. belongs_to :company
  9. belongs_to :user
  10. end
  11. # app/models/company.rb
  12. class Company &lt; ApplicationRecord
  13. has_many :employees, dependent: :destroy
  14. has_many :users, through: :employees
  15. end
  1. Company.create!(name: &quot;one&quot;, users: [User.new, User.new])
  2. Company.create!(name: &quot;two&quot;, users: [User.new, User.new])
  3. Employee.find(3).update(published: true)
  4. &gt;&gt; Employee.all.as_json
  5. =&gt; [{&quot;id&quot;=&gt;1, &quot;published&quot;=&gt;nil, &quot;company_id&quot;=&gt;1, &quot;user_id&quot;=&gt;1},
  6. {&quot;id&quot;=&gt;2, &quot;published&quot;=&gt;nil, &quot;company_id&quot;=&gt;1, &quot;user_id&quot;=&gt;2},
  7. {&quot;id&quot;=&gt;3, &quot;published&quot;=&gt;true, &quot;company_id&quot;=&gt;2, &quot;user_id&quot;=&gt;3},
  8. {&quot;id&quot;=&gt;4, &quot;published&quot;=&gt;nil, &quot;company_id&quot;=&gt;2, &quot;user_id&quot;=&gt;4}]
  1. # app/policies/employee_policy.rb
  2. class EmployeePolicy &lt; ApplicationPolicy
  3. class Scope &lt; Scope
  4. def resolve
  5. scope.where(company: user.company_ids).or(
  6. scope.where(published: true)
  7. )
  8. end
  9. end
  10. end

User #1 can see every employee from company #1 and only published from company #2:

  1. &gt;&gt; Pundit.policy_scope(User.first, Employee).as_json
  2. =&gt; [{&quot;id&quot;=&gt;1, &quot;published&quot;=&gt;nil, &quot;company_id&quot;=&gt;1, &quot;user_id&quot;=&gt;1},
  3. {&quot;id&quot;=&gt;2, &quot;published&quot;=&gt;nil, &quot;company_id&quot;=&gt;1, &quot;user_id&quot;=&gt;2},
  4. {&quot;id&quot;=&gt;3, &quot;published&quot;=&gt;true, &quot;company_id&quot;=&gt;2, &quot;user_id&quot;=&gt;3}]

User from company #2 can't see other company employees:

  1. &gt;&gt; Pundit.policy_scope(User.third, Employee).as_json
  2. =&gt; [{&quot;id&quot;=&gt;3, &quot;published&quot;=&gt;true, &quot;company_id&quot;=&gt;2, &quot;user_id&quot;=&gt;3},
  3. {&quot;id&quot;=&gt;4, &quot;published&quot;=&gt;nil, &quot;company_id&quot;=&gt;2, &quot;user_id&quot;=&gt;4}]

Scope that is not related to authorization should be applied in a controller:

  1. &gt;&gt; employee_scope = Pundit.policy_scope(User.first, Employee)
  2. &gt;&gt; employee_scope.where(company: Company.first).as_json
  3. =&gt; [{&quot;id&quot;=&gt;1, &quot;published&quot;=&gt;nil, &quot;company_id&quot;=&gt;1, &quot;user_id&quot;=&gt;1},
  4. {&quot;id&quot;=&gt;2, &quot;published&quot;=&gt;nil, &quot;company_id&quot;=&gt;1, &quot;user_id&quot;=&gt;2}]
  5. # user #1 authorization to see their own company employees didn&#39;t change
  6. # just currently looking at the other company
  7. &gt;&gt; employee_scope.where(company: Company.second).as_json
  8. =&gt; [{&quot;id&quot;=&gt;3, &quot;published&quot;=&gt;true, &quot;company_id&quot;=&gt;2, &quot;user_id&quot;=&gt;3}]

huangapple
  • 本文由 发表于 2023年4月17日 00:02:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/76028834.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定