英文:
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:
def index
@employees = if current_user.is_employee?(@company)
@company.employees.all
else
@company.employees.select { |employee| employee.published == true }
end
end
This works, but I'm trying to understand how to utilize Pundit
s 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:
# company.rb
has_many :employees, dependent: :destroy
has_many :users, through: :employees
# employee.rb
belongs_to :company
# 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:
def index
@employees = if current_user.is_employee?(@company)
@company.employees.all
else
@company.employees.select { |employee| employee.published == true }
end
end
This works, but I'm trying to understand how to utilise Pundit
s 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?
答案1
得分: 1
# app/models/user.rb
class User < ApplicationRecord
has_many :employees, dependent: :destroy
has_many :companies, through: :employees
end
# app/models/employee.rb
class Employee < ApplicationRecord
belongs_to :company
belongs_to :user
end
# app/models/company.rb
class Company < ApplicationRecord
has_many :employees, dependent: :destroy
has_many :users, through: :employees
end
Company.create!(name: "one", users: [User.new, User.new])
Company.create!(name: "two", users: [User.new, User.new])
Employee.find(3).update(published: true)
>> Employee.all.as_json
=> [{"id"=>1, "published"=>nil, "company_id"=>1, "user_id"=>1},
{"id"=>2, "published"=>nil, "company_id"=>1, "user_id"=>2},
{"id"=>3, "published"=>true, "company_id"=>2, "user_id"=>3},
{"id"=>4, "published"=>nil, "company_id"=>2, "user_id"=>4}]
# app/policies/employee_policy.rb
class EmployeePolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.where(company: user.company_ids).or(
scope.where(published: true)
)
end
end
end
User #1 can see every employee from company #1 and only published
from company #2:
>> Pundit.policy_scope(User.first, Employee).as_json
=> [{"id"=>1, "published"=>nil, "company_id"=>1, "user_id"=>1},
{"id"=>2, "published"=>nil, "company_id"=>1, "user_id"=>2},
{"id"=>3, "published"=>true, "company_id"=>2, "user_id"=>3}]
User from company #2 can't see other company employees:
>> Pundit.policy_scope(User.third, Employee).as_json
=> [{"id"=>3, "published"=>true, "company_id"=>2, "user_id"=>3},
{"id"=>4, "published"=>nil, "company_id"=>2, "user_id"=>4}]
Scope that is not related to authorization should be applied in a controller:
>> employee_scope = Pundit.policy_scope(User.first, Employee)
>> employee_scope.where(company: Company.first).as_json
=> [{"id"=>1, "published"=>nil, "company_id"=>1, "user_id"=>1},
{"id"=>2, "published"=>nil, "company_id"=>1, "user_id"=>2}]
# user #1 authorization to see their own company employees didn't change
# just currently looking at the other company
>> employee_scope.where(company: Company.second).as_json
=> [{"id"=>3, "published"=>true, "company_id"=>2, "user_id"=>3}]
英文:
# app/models/user.rb
class User < ApplicationRecord
has_many :employees, dependent: :destroy
has_many :companies, through: :employees
end
# app/models/employee.rb
class Employee < ApplicationRecord
belongs_to :company
belongs_to :user
end
# app/models/company.rb
class Company < ApplicationRecord
has_many :employees, dependent: :destroy
has_many :users, through: :employees
end
Company.create!(name: "one", users: [User.new, User.new])
Company.create!(name: "two", users: [User.new, User.new])
Employee.find(3).update(published: true)
>> Employee.all.as_json
=> [{"id"=>1, "published"=>nil, "company_id"=>1, "user_id"=>1},
{"id"=>2, "published"=>nil, "company_id"=>1, "user_id"=>2},
{"id"=>3, "published"=>true, "company_id"=>2, "user_id"=>3},
{"id"=>4, "published"=>nil, "company_id"=>2, "user_id"=>4}]
# app/policies/employee_policy.rb
class EmployeePolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.where(company: user.company_ids).or(
scope.where(published: true)
)
end
end
end
User #1 can see every employee from company #1 and only published
from company #2:
>> Pundit.policy_scope(User.first, Employee).as_json
=> [{"id"=>1, "published"=>nil, "company_id"=>1, "user_id"=>1},
{"id"=>2, "published"=>nil, "company_id"=>1, "user_id"=>2},
{"id"=>3, "published"=>true, "company_id"=>2, "user_id"=>3}]
User from company #2 can't see other company employees:
>> Pundit.policy_scope(User.third, Employee).as_json
=> [{"id"=>3, "published"=>true, "company_id"=>2, "user_id"=>3},
{"id"=>4, "published"=>nil, "company_id"=>2, "user_id"=>4}]
Scope that is not related to authorization should be applied in a controller:
>> employee_scope = Pundit.policy_scope(User.first, Employee)
>> employee_scope.where(company: Company.first).as_json
=> [{"id"=>1, "published"=>nil, "company_id"=>1, "user_id"=>1},
{"id"=>2, "published"=>nil, "company_id"=>1, "user_id"=>2}]
# user #1 authorization to see their own company employees didn't change
# just currently looking at the other company
>> employee_scope.where(company: Company.second).as_json
=> [{"id"=>3, "published"=>true, "company_id"=>2, "user_id"=>3}]
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论