简体   繁体   中英

Avoiding n+1 query on rails STI with different relationships

So let's say I have the following models:

class Building < ActiveRecord::Base
  has_many :rooms
  has_many :training_rooms, class_name: 'TrainginRoom', source: rooms
  has_many :computers, through: :training_rooms
end

class Computer < ActiveRecord::Base
  belongs_to :room
end

class Room < ActiveRecord::Base
  belongs_to :building
end

class Office < Room
end

class TrainingRoom < Room
  has_many :computers
end

And let's also say I am following the jsonapi spec and using the included top-level member to render each related object in a single http call.

So buildings/show looks sort of like this:

json.data do
  json.id building.id
  json.type building.type
  json.attributes do
    # render the attributes
  end
  json.relationships do
    # render the relationships
  end
end

json.included.do
  json.array! building.rooms.each do |room|
    json.type room.type
    json.id  room.id
    json.attributes do
     # render the attribtues
    end

    json.relationships do |room|
      # render included member's relationships
      # N+1 Be here
    end
  end
end

I have not been able to eagerly load the relationships from the included member, since it is not present on all members of the array.

For instance, in the controller:

@building = Building.eager_load(
  { rooms: :computers }
).find(params[:id])

Will not work if there is an office in the rooms relationship, as it does not have a computers relationship.

@building = Building.eager_load(
  :rooms,
  traning_rooms: :computers
).find(params[:id])

Also does not work, as the training rooms relationship provides access to the computers to be sideloaded, but is not accessed directly in the rendering code and thus is a useless cache.

Additionally I tried applying a default scope to training room to eager load the computers association, but that also didn't have the desired affect.

The only thing I can think of at this point is to apply the computers relationship to the Room class, but I don't really want to do it, because only training rooms should have computers.

I'd love to hear any thoughts.

Since there is no training_room_id in the Computer model, you will have to explicitly mention the foreign_key while defining the relationship.

class TrainingRoom < Room
  has_many :computers, foreign_key: :room_id
end

Now you will be able to eager load the records:

@building = Building.eager_load({ training_rooms: :computers }).where(id: params[:id])

Hope it helps !

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM