如何避免 Pundit 策略中的 N+1 显示?/更新?/销毁?

Vik*_*nic 6 activeadmin rolify pundit

我将 ActiveAdmin gem 与 Pundit(和 Rolify)gem 一起使用。

这就是我编写策略的方式(取自: https: //github.com/activeadmin/activeadmin/blob/master/spec/support/templates/policies/application_policy.rb):

class ApplicationPolicy
  attr_reader :user, :record

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

  def show?
    scope.where(id: record.id).exists?
  end

  def create?
    user.has_role?(:staff, record.company)
  end

  def update?
    scope.where(id: record.id).exists?
  end

  def destroy?
    scope.where(id: record.id).exists?
  end

  def destroy_all?
    true
  end

  def scope
    Pundit.policy_scope!(user, record.class)
  end

  class Scope
    attr_reader :user, :scope

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

    def resolve
      if user.admin?
        scope.all
      else
        company_ids = Company.with_role(:staff, user).map(&:id)
        scope.where(company_id: company_ids)
      end
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

这样就导致每次N+1次查询scope.where(id: record.id).exists?。在索引页上,show?为表中的每条记录调用 、update?和。destroy?

这种情况下如何避免N+1查询呢?

我正在尝试:1)与用户一起包含/预加载角色以进行调用current_user 2)我正在尝试记住或使用某种数组方法来防止使用和方法scope击中数据库。但仍然对每个新行进行数据库查询。whereexists?scope.find

谢谢!

Fiv*_*ell 2

首先,我建议向User对象添加一个方法来返回 company_ids ,这对员工有帮助。

class User #or AdminUser right?
  def company_ids
      @company_ids ||= Company.with_role(:staff, self).map(&:id)
  end
end

Run Code Online (Sandbox Code Playgroud)

你无法改变

 def destroy?
    scope.where(id: record.id).exists?
  end
Run Code Online (Sandbox Code Playgroud)

 def destroy?
    return true user.admin?
    user.company_ids.include?(record.company_id)
  end
Run Code Online (Sandbox Code Playgroud)

范围的解析方法现在看起来像这样

def resolve
      if user.admin?
        scope.all
      else
        scope.where(company_id: user.company_ids)
      end
    end
  end
Run Code Online (Sandbox Code Playgroud)