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