Reputation: 2497
I have a rails API that currently has quite a few N+1 queries that I'd like to reduce.
As you can see it's going through quite a few loops before returning the data.
The relationships are as follows:
class Company < ApplicationRecord
has_many :jobs, dependent: :destroy
has_many :contacts, dependent: :destroy
has_many :listings
end
class Job < ApplicationRecord
belongs_to :company
has_many :listings
has_and_belongs_to_many :technologies
has_and_belongs_to_many :tools
scope :category, -> ( category ) { where category: category }
end
class Listing < ApplicationRecord
belongs_to :job, dependent: :destroy
belongs_to :company, dependent: :destroy
scope :is_active, -> ( active ) { where is_active: active }
end
class SimpleJobSerializer < ActiveModel::Serializer
attributes :id,
:title,
:company_name,
attribute :technology_list, if: :technologies_exist
attribute :tool_list, if: :tools_exist
def technology_list
custom_technologies = []
object.technologies.each do |technology|
custom_technology = { label: technology.label, icon: technology.icon }
custom_technologies.push(custom_technology)
end
return custom_technologies
end
def tool_list
custom_tools = []
object.tools.each do |tool|
custom_tool = { label: tool.label, icon: tool.icon }
custom_tools.push(custom_tool)
end
return custom_tools
end
def tools_exist
return object.tools.any?
end
def technologies_exist
return object.technologies.any?
end
def company_name
object.company.name
end
end
Job.eager_load(:listings).order("listings.live_date DESC").where(category: "developer", listings: { is_active: true }).first(90)
I've tried to use eager_load
to join the listings to the Jobs to make the request more efficient but i'm unsure how to handle this when some of the n+1 queries are coming from inside the serializer as it tries to look at tools and technologies.
Any help would be much appreciated!
Upvotes: 0
Views: 888
Reputation: 102240
You might was well eager load tools and technologies since you know that the serializer is going to use them:
Job.eager_load(:listings, :tools, :technologies)
.order("listings.live_date DESC")
.where(category: "developer", listings: { is_active: true })
.first(90)
After that you really need to refactor that serializer. #each
should only be used when you are only interested in the side effects of the iteration and not the return value. Use #map
, #each_with_object
, #inject
etc. These calls can be optimized. return
is implicit in ruby so you only explicitly return if you are bailing early.
class SimpleJobSerializer < ActiveModel::Serializer
# ...
def tool_list
object.tools.map { |t| { label: tool.label, icon: tool.icon } }
end
# ...
end
Upvotes: 1
Reputation: 18484
Try nested preload:
Job.preload(:technologies, :tools, company: :listings).order(...).where(...)
Upvotes: 0