I'm using ActiveAdmin gem together with Pundit (and Rolify) gem.
This is how I wrote my policy (taken from: 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
This causes N+1 query each time scope.where(id: record.id).exists?
. On index page, show?
, update?
and destroy?
are called for each record in the table.
How can I avoid the N+1 query in this case?
I'm trying to:
1) Include/preload roles together with user for calls to current_user
2) I'm trying to memoize the scope
or use some array method to prevent hitting the db with where
and exists?
methods. But scope.find
still makes the db query for every new row.
Thanks!
first of all, I suggest adding a method to the User
object to return company_ids where it is staff helps.
class User #or AdminUser right?
def company_ids
@company_ids ||= Company.with_role(:staff, self).map(&:id)
end
end
than you can change
def destroy?
scope.where(id: record.id).exists?
end
to
def destroy?
return true user.admin?
user.company_ids.include?(record.company_id)
end
and resolve method for Scope now looks this way
def resolve
if user.admin?
scope.all
else
scope.where(company_id: user.company_ids)
end
end
end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With