-
Notifications
You must be signed in to change notification settings - Fork 60
Description
I'm using JSON API include
to side load two associations (two levels deep: company,company.company-master
) and I'm seeing an N+1 issue when plugging in Pundit + JSONAPI Athorization into my JSONAPI Resources Processor stack.
I've tried explicitly including those two associations in the policy scope with no success, although this shouldn't be required since JSONAPI Resources should be handling this. It does when JSONAPI Authorization isn't involved.
The logs produced:
Started GET "/api/v1/job-applications?include=company%2Ccompany.company-master" for ::1 at 2017-09-20 18:11:06 -0700
[NAME COLLISION] `type` is a reserved key in EventResource.
[NAME COLLISION] `type` is a reserved key in AccountResource.
Processing by Api::V1::JobApplicationsController#index as JSON
Parameters: {"include"=>"company,company.company-master"}
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 6], ["LIMIT", 1]]
JobApplication Load (1.4ms) SELECT "job_applications".* FROM "job_applications" WHERE "job_applications"."user_id" = 6 ORDER BY "job_applications"."id" ASC LIMIT $1 OFFSET $2 [["LIMIT", 1000], ["OFFSET", 0]]
Events::Applied Load (1.6ms) SELECT "events".* FROM "events" WHERE "events"."type" IN ('Events::Applied') AND "events"."subject_type" = $1 AND "events"."subject_id" IN (465, 466, 467, 468, 479) [["subject_type", "JobApplication"]]
Source Load (0.7ms) SELECT "sources".* FROM "sources" WHERE "sources"."id" = 21
(1.8ms) SELECT "job_applications"."id", "companies"."id" FROM "job_applications" INNER JOIN "companies" ON "companies"."id" = "job_applications"."company_id" LEFT OUTER JOIN "events" ON "events"."subject_id" = "job_applications"."id" AND "events"."type" IN ('Events::Applied') AND "events"."subject_type" = $1 LEFT OUTER JOIN "sources" ON "sources"."id" = "job_applications"."source_id" WHERE "job_applications"."user_id" = 6 AND "job_applications"."id" IN (465, 466, 467, 468, 479) [["subject_type", "JobApplication"]]
Company Load (1.0ms) SELECT "companies".* FROM "companies" WHERE "companies"."user_id" = 6 AND "companies"."id" IN (260, 285, 286, 287, 288)
CompanyMaster Load (1.3ms) SELECT "company_masters".* FROM "company_masters" WHERE "company_masters"."id" IN (338, 312, 79, 301, 359)
(1.8ms) SELECT "job_applications"."id", "companies"."id", "company_masters"."id" FROM "job_applications" INNER JOIN "companies" ON "companies"."id" = "job_applications"."company_id" INNER JOIN "company_masters" ON "company_masters"."id" = "companies"."company_master_id" LEFT OUTER JOIN "events" ON "events"."subject_id" = "job_applications"."id" AND "events"."type" IN ('Events::Applied') AND "events"."subject_type" = $1 LEFT OUTER JOIN "sources" ON "sources"."id" = "job_applications"."source_id" WHERE "job_applications"."user_id" = 6 AND "job_applications"."id" IN (465, 466, 467, 468, 479) [["subject_type", "JobApplication"]]
CompanyMaster Load (1.7ms) SELECT "company_masters".* FROM "company_masters" WHERE "company_masters"."id" IN (338, 312, 79, 301, 359)
(0.6ms) SELECT COUNT(*) FROM "job_applications" WHERE "job_applications"."user_id" = 6
Company Load (0.6ms) SELECT "companies".* FROM "companies" WHERE "companies"."id" = $1 LIMIT $2 [["id", 287], ["LIMIT", 1]]
CompanyMaster Load (0.5ms) SELECT "company_masters".* FROM "company_masters" WHERE "company_masters"."id" = $1 LIMIT $2 [["id", 301], ["LIMIT", 1]]
Company Load (0.9ms) SELECT "companies".* FROM "companies" WHERE "companies"."id" = $1 LIMIT $2 [["id", 288], ["LIMIT", 1]]
CompanyMaster Load (3.7ms) SELECT "company_masters".* FROM "company_masters" WHERE "company_masters"."id" = $1 LIMIT $2 [["id", 359], ["LIMIT", 1]]
Company Load (0.6ms) SELECT "companies".* FROM "companies" WHERE "companies"."id" = $1 LIMIT $2 [["id", 286], ["LIMIT", 1]]
CompanyMaster Load (2.6ms) SELECT "company_masters".* FROM "company_masters" WHERE "company_masters"."id" = $1 LIMIT $2 [["id", 79], ["LIMIT", 1]]
Company Load (0.6ms) SELECT "companies".* FROM "companies" WHERE "companies"."id" = $1 LIMIT $2 [["id", 285], ["LIMIT", 1]]
CompanyMaster Load (0.7ms) SELECT "company_masters".* FROM "company_masters" WHERE "company_masters"."id" = $1 LIMIT $2 [["id", 312], ["LIMIT", 1]]
Company Load (0.5ms) SELECT "companies".* FROM "companies" WHERE "companies"."id" = $1 LIMIT $2 [["id", 260], ["LIMIT", 1]]
CompanyMaster Load (0.6ms) SELECT "company_masters".* FROM "company_masters" WHERE "company_masters"."id" = $1 LIMIT $2 [["id", 338], ["LIMIT", 1]]
Rendering text template
Rendered text template (0.4ms)
Completed 200 OK in 576ms (Views: 1.7ms | ActiveRecord: 45.0ms)
As you can see, the side loaded records are fetched as normal. However, after that, individual queries are performed, which shouldn't be happening.
This is my policy:
class JobApplicationPolicy < ApplicationPolicy
def index?
current_user.has_role?(:jobseeker)
end
alias_method :new?, :index?
alias_method :edit?, :index?
alias_method :create?, :index?
alias_method :update?, :index?
alias_method :destroy?, :index?
class Scope < Scope
def resolve
# Applied Event is included because model delegates `date_applied` to `applied_event`
new_scope = scope.includes(:applied_event, :source)
if current_user.has_role?(:admin) || current_user.has_role?(:advisor)
new_scope.joins(user: [:account]).where(accounts: { id: current_user.account.id })
else
new_scope.where(user: current_user)
end
end
end
end
The scope returned should already include the includes(company: [:company_master])
, and explicitly adding it doesn't resolve the issue.