Conditionally loading polymorphic associations

I am trying to eliminate some N + 1 requests for a user feed on my site. There are various models that have a polymorphic relationship with the 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 

The user model is also involved when the user has several tournaments and events that have different attributes. When loading FeedItems for a user, how can I confidently load nested attributes based on that type of FeedItem?

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 request is trying to get all feeds such as Tournament and Event, trying to load the game attribute of the Tournament model. Perfect for tournaments, but if you repeat on FeedItems, events will throw an error that this attribute does not exist.

My current hack is to make separate requests for each type of FeedItem and add entries together. However, this converts the association into an array, which I do not want to do.

Any ideas?

+7
ruby-on-rails activerecord ruby-on-rails-4
source share
1 answer

You can preset 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 pre-set the 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 can get the associated game of the tournament without an additional request:

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

All Articles