You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails 7中如何通过Pundit Scope实现多对多关联的员工权限控制?

问题:如何用Pundit Scope实现公司员工列表的权限控制

模型间多对多关联如下:

# company.rb
has_many :employees, dependent: :destroy
has_many :users, through: :employees

# employee.rb
belongs_to :company

# user ...

Employee有published(已发布)和未发布两种状态,需求为:

  • 普通用户在公司员工列表页仅能查看已发布的Employee
  • 该公司的员工可查看全部Employee

目前在employees_controller#index中的实现逻辑:

def index
  @employees = if current_user.is_employee?(@company)
                 @company.employees.all
               else
                 @company.employees.select { |employee| employee.published == true }
               end
end

希望通过Pundit的Scope实现相同逻辑,但无法从Scope中获取公司信息,尝试的伪代码如下:

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

  def resolve
    if user.is_employee?(...) # 无法从scope中获取公司信息
      scope.all
    else
      scope.where(published: true)
    end
  end
end

不想使用繁琐的join方法,求其他简洁的实现方式。


解决方案

这里提供两种实用的实现思路:

方法一:调用Scope时传入公司实例

Pundit支持在初始化Scope时传递额外上下文参数,你可以修改Scope的初始化方法,直接传入目标公司实例:

  1. 修改EmployeePolicy的Scope类:
# app/policies/employee_policy.rb
class EmployeePolicy < ApplicationPolicy
  class Scope
    attr_reader :user, :scope, :company

    def initialize(user, scope, company)
      @user = user
      @scope = scope
      @company = company
    end

    def resolve
      if user.is_employee?(company)
        scope.all
      else
        scope.where(published: true)
      end
    end
  end
end
  1. 在控制器中调用policy_scope时传入公司实例:
def index
  @company = Company.find(params[:company_id])
  @employees = policy_scope(@company.employees, company: @company)
end

注:Rails 6+可直接通过policy_scope第三个参数传递额外参数;旧版本可使用policy_scope(@company.employees, context: { company: @company }),再在Scope初始化时从context中提取。

方法二:从Scope中推导公司信息

如果不想额外传递参数,也可以从当前Scope的ActiveRecord关系中提取公司信息——因为你的Scope是@company.employees,本质上是带有company_id条件的查询集合,可直接获取对应的公司:

# app/policies/employee_policy.rb
class EmployeePolicy < ApplicationPolicy
  class Scope
    attr_reader :user, :scope

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

    def resolve
      # 从scope的查询条件中提取company_id
      company_id = scope.where_values.detect do |cond|
        cond.is_a?(Arel::Nodes::Equality) && cond.left.name == 'company_id'
      end&.right&.value
      company = Company.find(company_id) if company_id

      if company && user.is_employee?(company)
        scope.all
      else
        scope.where(published: true)
      end
    end
  end
end

控制器中正常调用即可:

def index
  @company = Company.find(params[:company_id])
  @employees = policy_scope(@company.employees)
end

提示:原控制器中的select方法会加载所有员工再过滤,建议改成where查询,提升性能:@company.employees.where(published: true)

内容的提问来源于stack exchange,提问作者Anders

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 22:32:37