Polymorphic Association With Has_many :through

Hi Folks,

In Rails we have Ploymorphic Association whenever we need to connect one model to more than one model. I had a situation where i need polymorphic association with has_many :through Here I am showing an simple example of polymorphic association with has_many :through

We have Contact , Plan and Template as a ActiveRecord Classes.

We have following scenarios.

  1. Plan has_many Contacts
  2. Contact has_many Plans
  3. Template has_many Contacts
  4. Contact has_many Templates which is vice versa of 3.

So here is the main problem. Here to solve this we need two more tables other then Plans , Contants and Templates.

My Plan Model looks like:

1
2
3
4
class Plan < ActiveRecord::Base
  has_many :plan_contacts
  has_many :contacts ,:through => :plan_contacts
end

My Template Model looks like:

1
2
3
4
class Template < ActiveRecord::Base
  has_many :template_contacts
  has_many :contacts ,:through => :template_contacts
end

My Contact Model looks like:

1
2
3
4
5
6
7
8
class Contact < ActiveRecord::Base
  has_many :template_contacts
  has_many :plan_contacts

  has_many :templates ,:through => :template_contacts
  has_many :plans ,:through => :plan_contacts

end

And here are two different tables for handling many_to_many

Handling Plan and Contact here.

1
2
3
4
class PlanContact < ActiveRecord::Base
  belongs_to :plan
  belongs_to :contact
end

Handling Template and Contact here.

1
2
3
4
class TemplateContact < ActiveRecord::Base
  belongs_to :template
  belongs_to :contact
end

This problem can be solved using only one table. Just we need to mark that table as a polymorphic.

We are calling that tables as contact_details. Here is class looks like.

1
2
3
4
class ContactDetail < ActiveRecord::Base
  belongs_to :contactable, :polymorphic => true
  belongs_to :contact
end

Migration should look like:

1
2
3
4
5
6
7
8
9
10
class CreateContactDetails < ActiveRecord::Migration
  def change
    create_table :contact_details do |t|
      t.integer :contact_id
      t.integer :contactable_id
      t.string :contactable_type
      t.timestamps
    end
  end
end

Now change your Contact model like:

1
2
3
4
5
6
class Contact < ActiveRecord::Base
  has_many :plans ,:through => :contact_details, :source => :contactable, :source_type => 'Plan'
  has_many :templates ,:through => :contact_details, :source => :contactable, :source_type => 'Template'
  has_many :contact_details
  accepts_nested_attributes_for :plans,:templates
end

Now change your Plan model like:

1
2
3
4
  class Plan < ActiveRecord::Base
    has_many :contacts,:through => :contact_details
    has_many :contact_details, :as => :contactable
  end

Same way Template model like:

1
2
3
4
  class Template < ActiveRecord::Base
    has_many :contacts,:through => :contact_details
    has_many :contact_details, :as => :contactable
  end

So you can see we can use one table(contact_details) for both plan and template

1
2
3
4
5
6
7
8
9
10
11
 contact  = Contact.create(:name => 'Rays')
 template = Template.create(:name => 'template 1')
 plan = Plan.create(:name =>'plan')

 contact.plans << plan
 contact.templates << template

 template.contacts => [contact]
 plan.contacts => [contact]
 contact.templates => [template]
 contact.plans => [plans]

so if you have accepts_nested_attributes_for :template then you can create template for contact in a form

1
 Contact.create(:name => 'abc',:templates_attributes => {'0' => {'name' => 'Tech'}})

it will create an entry in templates table and and entry in contact_details

So in this way we dont need to write much code.we can reduce tables number

I hope it will be helpful for you

Reference links :