I have a rails application with deeply nested associations.
.-< WorkPeriod
Timecard -< Week -< Day -<--< Subtotal
`-< Adjustment
-< (has many)
I'm using Active Model Serializer to build out the API.
On the client side I want to load a timecard and all it's associations in one shot.
Currently my serializers look like this,
class TimecardSerializer < ActiveModel::Serializer
embed :ids, include: true
has_many :weeks
end
class WeekSerializer < ActiveModel::Serializer
embed :ids, include: true
has_many :days
end
# ... etc ...
This all works find, except nothing gets eager-loaded. So it ends up making lots of calls to the database for each request. For each week, it makes a separate request for the days in that week. And for each day, it makes a separate request for it's work_periods, subtotals, and adjustments.
What is Active Model Serializers? Serializer gem allows us to format our JSON easily. It will enable us to select only the data we want and access our relationships with a single request. Active Model Serializers provides a way of creating custom JSON in an object-oriented manner.
Serialization converts an object in memory into a stream of bytes that can be recreated when needed. Serializers in Ruby on Rails convert a given object into a JSON format. Serializers control the particular attributes rendered when an object or model is converted into a JSON format.
One solution is to define your own weeks
method on the TimecardSerializer. From there you can .includes()
all the associations you want to eager load.
class TimecardSerializer < ActiveModel::Serializer
embed :ids, include: true
has_many :weeks
def weeks
object.weeks.includes(days: [:sub_totals, :work_periods, :adjustments])
end
end
All the queries will still show up in the log but most will be a cached query instead of a real one.
I had a similar issue. I fixed it in my controller. I like the idea of putting it in the serializer, but having it in the controller catches the n+1 weeks problem created by the ArraySerializer too.
Timecard.find(params[:id]).includes(weeks: [{ days: [:sub_totals, :work_periods, :adjustments] }])
and
Timecard.includes(weeks: [{ days: [:sub_totals, :work_periods, :adjustments] }])
should now eager load and limit the query to just six db hits.
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