Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Eager load associations with Active Model Serializers

Background

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 ...

Problem

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.

like image 755
Ryan Avatar asked Aug 08 '13 19:08

Ryan


People also ask

What is Active model Serializers?

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.

What is the use of Serializers in rails?

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.


2 Answers

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.

like image 181
Ryan Avatar answered Sep 18 '22 14:09

Ryan


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.

like image 24
James Avatar answered Sep 20 '22 14:09

James