So I checked this interesting topic and will tell what I know.
When you create objects as usual in has_many :through
:
class Contact
has_many :contactables
has_many :managers, :through => :contactables
end
class Manager
has_many :contactables
has_many :contacts, :through => :contactables
end
class Client
has_many :contactables
has_many :contacts, :through => :contactables
end
class Contactable
belongs_to :contact
belongs_to :manager
belongs_to :client
end
You get to use foreign keys fro each referenced object. Polymorphic looks like a great solution. So:
class Contactable
belongs_to :contact
belongs_to :polymorphic_model, polymorphic: true
end
class Contact
has_many :contactables
has_many :managers, :through => :contactables, :source => :polymorphic_model, :source_type => 'Manager'
end
class Manager
has_many :contactables, :as => :polymorphic_model
has_many :contacts, :through => :contactables
end
Setting the :as option indicates that this is a polymorphic
association
:source => :polymorphic_model
is used to tell Rails to get the related object from the subclass. :source
means the same as :class_name
. Without this option Rails would try to get associated Manager from the Contactables table, while it should be reached via virtual Polymorphic_model.
By adding belongs_to :polymorphic_model
to Contactable you enable Contact (witch already sits there, because of belongs_to :contact
) to be associated with a Manager or Client, because thats what Polymorphic association does - references two or more parent tables. And because Contact have_many
Contactables, the same Contact object can be associated with many managers or clients. So after you understand it, it looks really simple - Joined model belongs to Contact and Joined model also holds references to Manager and Client through Polymorphic association. So in order for Contact to have many managers, you create another Contactable object that belongs to the same Contact, but different Manager. Doesn't look super efficient, but personally me, not knowing a better way..
Here is a tested proof:
Manager.create!(name: "Bravo")
=> #<Manager id: 1, created_at: "2017-04-12 12:17:41", updated_at: "2017-04-12 12:17:41", name: "Bravo">
Manager.create!(name: "Johnny")
=> #<Manager id: 2, created_at: "2017-04-12 12:18:24", updated_at: "2017-04-12 12:18:24", name: "Johnny">
Contact.create!(number:"123")
=> #<Contact id: 1, created_at: "2017-04-12 12:18:59", updated_at: "2017-04-12 12:18:59", number: 123>
c = Contactable.new
c.contact = Contact.first
c.unit = Manager.first
c
=> #<Contactable id: nil, unit_type: "Manager", created_at: nil, updated_at: nil, unit_id: 1, contact_id: 1>
Now to set another Manager to the same contact, we create a new Contactable:
cc = Contactable.new
cc.contact = Contact.first
cc.unit = Manager.last
cc
=> #<Contactable id: nil, unit_type: "Manager", created_at: nil, updated_at: nil, unit_id: 4, contact_id: 1>
And to get all associated:
Contact.first.managers
Contactable's database:
contact_id
unit_id
unit_type
And one interesting quote by @Bill Karwin:
The Polymorphic Associations design breaks rules of relational
database design. I don't recommend using it.
But he wrote this long time ago. Probably irrelevant now.
Why can you not have a foreign key in a polymorphic association?