1

A Manager has many contacts via polymorphic association

class Manager
  has_many :contacts, as: :contactable
end

class Contact
  belongs_to :contactable, polymorphic: true
end

The relation works fine but now a contact can be associated to many managers.

So, added a new model Contactable, a joins table 'contactables' and moved contactable_id and contactable_type fields from contacts table to contactables table.

class Contactable
  belongs_to :contact
  belongs_to :contactable, polymorphic: true
end

Now confused about the Manager and Contact relation that how it would be defined in models correctly to make it working. Tried the following but it doesn't work:

class Manager
  has_many :contacts, through: :contactables, source: :contactable, source_type: "Contact"
end 
Ilya Lavrov
  • 2,810
  • 3
  • 20
  • 37
Arif
  • 1,369
  • 14
  • 39

1 Answers1

1

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?

Community
  • 1
  • 1
Julius Dzidzevičius
  • 10,775
  • 11
  • 36
  • 81
  • I think we need the `source_type` as well along with `source` option in `Contact` model. i.e `:source_type => 'Manager'` – Arif Apr 11 '17 at 15:09
  • Yes, I haven't added that before, because it was kinda optional - just to make life easier. Now included it as well – Julius Dzidzevičius Apr 11 '17 at 15:53