Home > Mobile >  Rails nested includes with dynamic class
Rails nested includes with dynamic class

Time:03-13

If I have a class A that has many B, but B is just the ID of a class, and can actually take the form of multiple different classes, can I eager load the associations of B dynamically based off what class it is?

For example, if B was the id of class Car, am I somehow able to eager load B.wheels. But also if it's of type Dog, can I get B.Toys? This would all be through A, which of only one class type. I tried with syntax:

notifs = current_user.notifications.includes(target: [:wager_members, :disputes, :games])

Where notifications would be of class A, :target would be the dynamic class B, and :wager_members, :disputes, and :games would be the associations depending on what class B is.

However, I get an error saying there is no class F for class B, which makes sense because class B dynamically changes. Is there a way/syntax to load all nested associations in one fell swoop?

I'm wondering if I need to rethink model associations to make it feasible.

CodePudding user response:

Polymorphic associations are key here: https://guides.rubyonrails.org/association_basics.html#polymorphic-associations

Here is roughly what your associations should look like at the class level

User
has_many :notifications

Notification
belongs_to :user
belongs_to :target

Target
belongs_to :notify, polymorphic: true # Can be any model

You should then be able to do something like this:

Notification.create(user: user, target: wager_member)
Notification.create(user: user, target: dispute)
Notification.create(user: user, target: game)

notifs = current_user.notifications.includes(target: [:wager_members, :disputes, :games])

notifs.first.target.notify.class.name # "WagerMember"

Really you can skip the target model and move the polymorphic association to the notification itself. Though may be useful if you need to add some custom logic. This results in this:

Notification
belongs_to :user
belongs_to :target, polymorphic: true

notifs.first.target.class.name # "WagerMember"
  • Related