← Back to blog
· 3 min read · activerecord association polymorphic

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

Here I am showing an simple example of polymorphic association with hasmany :through

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

We have following scenarios.

  1. Plan hasmany Contacts
  2. Contact hasmany Plans
  3. Template hasmany 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:

{% codeblock lang:ruby%} class Plan < ActiveRecord::Base hasmany :plancontacts hasmany :contacts ,:through => :plancontacts end {% endcodeblock %}

My Template Model looks like:

{% codeblock lang:ruby%} class Template < ActiveRecord::Base hasmany :templatecontacts hasmany :contacts ,:through => :templatecontacts end {% endcodeblock %}

My Contact Model looks like:

{% codeblock lang:ruby%} class Contact < ActiveRecord::Base hasmany :templatecontacts hasmany :plancontacts

hasmany :templates ,:through => :templatecontacts hasmany :plans ,:through => :plancontacts

end {% endcodeblock %}

And here are two different tables for handling manytomany

Handling Plan and Contact here.

{% codeblock lang:ruby%} class PlanContact < ActiveRecord::Base belongsto :plan belongsto :contact end {% endcodeblock %}

Handling Template and Contact here.

{% codeblock lang:ruby%} class TemplateContact < ActiveRecord::Base belongsto :template belongsto :contact end {% endcodeblock %}

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.

{% codeblock lang:ruby%} class ContactDetail < ActiveRecord::Base belongsto :contactable, :polymorphic => true belongsto :contact end {% endcodeblock %}

Migration should look like:

{% codeblock lang:ruby%} class CreateContactDetails < ActiveRecord::Migration def change createtable :contactdetails do |t| t.integer :contactid t.integer :contactableid t.string :contactable_type t.timestamps end end end

{% endcodeblock %}

Now change your Contact model like:

{% codeblock lang:ruby%} class Contact < ActiveRecord::Base hasmany :plans ,:through => :contactdetails, :source => :contactable, :sourcetype => 'Plan' hasmany :templates ,:through => :contactdetails, :source => :contactable, :sourcetype => 'Template' hasmany :contactdetails acceptsnestedattributes_for :plans,:templates end {% endcodeblock %}

Now change your Plan model like:

{% codeblock lang:ruby%} class Plan < ActiveRecord::Base hasmany :contacts,:through => :contactdetails hasmany :contactdetails, :as => :contactable end {% endcodeblock %}

Same way Template model like:

{% codeblock lang:ruby%} class Template < ActiveRecord::Base hasmany :contacts,:through => :contactdetails hasmany :contactdetails, :as => :contactable end {% endcodeblock %}

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

{% codeblock lang:ruby%} 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] {% endcodeblock %}

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

{% codeblock lang:ruby%} Contact.create(:name => 'abc',:templatesattributes => {'0' => {'name' => 'Tech'}}) {% endcodeblock %}

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 :