简体   繁体   中英

Conditionally eager loading polymorphic associations

I'm trying to eliminate some N+1 queries for a user feed on my site. There are various models that have a polymorphic association to a FeedItem model

class Event < ActiveRecord::Base
  has_many :feed_items, as: :feedable
end

class Tournament < ActiveRecord::Base
  has_many :feed_items, as: :feedable
end

class FeedItem < ActiveRecord::Base
  belongs_to :feedable, :polymorphic => true
end

A user model is also involved where a user has_many Tournaments and Events which both have different attributes. When loading FeedItems for a user how do I conditionally eager load the nested attributes based on which FeedItem type it is?

An example of what I mean (with squeel DSL):

FeedItem.where{ (feedable_id.in(@user.tournaments.ids}) & feedable_type.eq('Tournament')) |    
                (feedable_id.in(@user.events.ids) & feedable_type.eq('Event')) }.includes(:feedable => :game)

This query attempts to retrieve all FeedItems of type Tournament and Event while eager loading the 'game' attribute of the Tournament model. Works fine for Tournaments but when iterating over the FeedItems the events will throw an error that this attribute doesn't exist.

My current hack is to do separate queries for every FeedItem type and add the records together. However this converts the association into an array which I don't want to do.

Any ideas?

It's possible to preload nested associations for polymorphic associations. You need to add two more associations to FeedItem :

class FeedItem < ActiveRecord::Base
  belongs_to :feedable, :polymorphic => true

  belongs_to :event, -> { where(feedable_type: 'Event' ) }, foreign_key: :feedable_id
  belongs_to :tournament, -> { where(feedable_type: 'Tournament' ) }, foreign_key: :feedable_id
end

After that you can preload nested association only for tournament :

feeds =
  FeedItem.where("
    (feedable_id IN (?) AND feedable_type = 'Tournament') OR 
    (feedable_id IN (?) AND feedable_type = 'Event')", @user.tournament_ids, @user.event_ids).
    includes(:feedable, tournament: :game)

Now you may get related tournament game without additional query:

feeds.each do |feed|
  puts feed.feedable.inspect
  puts feed.tournament.game.inspect if feed.feedable_type == "Tournament"
end

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