Lets say, you wanted to store contact information such as emails, phone numbers, and websites. These contact information can be associated with an employee of a business or the business itself. A business can have multiple emails and multiple phone numbers. Similarly, an employee can have multiple emails and multiple phone numbers. In Rails, we can store all these contact information in a single table using both the polymorphic association and single-table inheritance. First, lets see how we can use polymorphic association to deal with the contact information.
We have the following active record models for a “Business” and an “Employee”:
class Business < ActiveRecord::Base; end
class Employee < ActiveRecord::Base; end
For the contact information, we have the following active record models:
class Email < ActiveRecord::Base; end
class PhoneNumber < ActiveRecord::Base; end
class Website < ActiveRecord::Base; end
Now, for the associations, Business and Employee models can have many emails, phone numbers and websites. And conversely, Email/Phone Number/Website can belong to either a Business model or an Employee model. How do we make up the association?
Rails ActiveRecord provides an association called “Polymorphic” association. Using polymorphic association, a model can belong to more than one other model, on a single association. Here is how this can be implemented:
class Business < ActiveRecord::Base
has_many :emails, :as => :emailable
has_many :phone_numbers, :as => phonable
has_many :websites, :as => webable
end
class Employee < ActiveRecord::Base
has_many :emails, :as => :emailable
has_many :phone_numbers, :as => phonable
has_many :websites, :as => webable
end
class Email < ActiveRecord::Base
belongs_to :emailable, :polymorphic => true
end
class PhoneNumber < ActiveRecord::Base
belongs_to :phonable, :polymorphic => true
end
class Website < ActiveRecord::Base
belongs_to :webable, :polymorphic => true
end
Having the above setup, you can get a collection of emails for a business instance via the call “@business.emails”. Similarly, you can get a collection of emails for an employee instance via “@employee.emails”.
Now, if you really think about what a contact information is? It basically contains a label and an information. I.e. Personal Email: test@gmail.com, Business Phone Number: 1-800-999-9999, Business Website: www.google.com, etc.
So, we can say that an email, a phone number, or a website is an instance of a contact information class with two attributes: a label and an information. Thus, we can store any type of contact information in a single table.
In rails, if you name a column “type”, by default ActiveRecord allows inheritance by storing the name of the class in the column type. You can overwrite the Base.inheritance_column to change the name of the column where the class name is stored. Now, changing our polymorphic associations as “contactable”, we can have the following setup:
class Business < ActiveRecord::Base
has_many :emails, :as => :contactable, :class_name => "Email"
has_many :phone_numbers, :as => :contactable, :class_name => "PhoneNumber"
has_many :websites, :as => :contactable, :class_name => "Website"
end
class Employee < ActiveRecord::Base
has_many :emails, :as => :contactable, :class_name => "Email"
has_many :phone_numbers, :as => :contactable, :class_name => "PhoneNumber"
has_many :websites, :as => :contactable, :class_name => "Website"
end
class ContactInformation < ActiveRecord::Base
belongs_to :contactable, :polymorphic => true
end
class Email < ContactInformation
# any validation for email can go here
end
class PhoneNumber < ContactInformation
# any validation for phone numbers can go here
end
class Website < ContactInformation
# any validation for website urls can go here
end
All the emails, phone numbers and websites will now be stored in the table associated with the model “ContactInformation” and will have the following setup:
create_table :contact_informations, :force => true do |t|
t.string "contactable_type"
t.integer "contactable_id"
t.string "type"
t.string "label"
t.string "info"
t.timestamps
end
Now, when you create a business email:
@email = @business.build.emails(:label => "Personal", :info => "test@gmail.com");
@email.save!
the record will be saved in the table “contact_informations” with the values “Business” for the “contactable_type”, @business.id for the “contactable_id”, and “Email” for the column “type”.
Similarly, for employee phone number:
@phone = @employee.build.phone_numbers(:label => "Work", :info => "1-800-123-4567");
@phone.save!
the record will be saved in the table “contact_informations” with the values “Employee” for the “contactable_type”, @employee.id for the “contactable_id”, and “PhoneNumber” for the column “type”.
Rails makes it really easy to do a single table inheritance with polymorphic association.
Thanks, that helped a lot. I’ve got into Rails at a time where polymorphic associations were a sure way to break everything. But now, I feel comfortable using them more often.
Thanks,,,,good work…please keep updating…
Nice explanation. Thanks for the post.
Just wanted to say thanks, this was perfect. I was getting an error for my polymorphic STI “addresses” about missing the addressable_id – well, I hadn’t created the table properly! Thanks for writing this out!
Well written article. Thanks.
One comment:
@email = @business.build.emails(:label => "Personal", :info => "test@gmail.com")
I think it should be:
@email = @business.emails.build(:label => "Personal", :info => "test@gmail.com")
Otherwise it will give an error : NoMethodError: undefined method `build’
Hi, What is the difference between polymorphic association and single table inheritance?
How would I implement counter_cache into this? Or will I have to manually keep up with it on update, and just select form that column for the count?
Let me explain a little more.
I have an assets class
class Asset < ActiveRecord::Base
belongs_to :documentable, polymorphic: true, counter_cache: true
end
Then Image, Video, and Document. Ex:
class Image < MediaFile
end
I have Images, Videos, and Documents for Products:
class Product < ActiveRecord::Base
has_many :images, { as: :documentable, class_name: 'Image' }
has_many :videos, { as: :documentable, class_name: 'Video' }
has_many :documents, { as: :documentable, class_name: 'Document' }
end
I just ended up doing this. Seems to work well.
before_save do |product|
product.images_count = product.images.size
product.videos_count = product.videos.size
product.documents_count = product.documents.size
end
Thank you.. I helped me alot.
Not very universal solution. It fits only in case we don’t care about different formats of data. F.e. we want to store phone as phone type – and not string. Thus, we could use nice Rails validations and so on. I think it’s not good idea to place together such a different data and store them as string together.
It’s hard to find your website in google. I found it on 22 spot, you should build quality backlinks , it will help you to get more visitors.
I know how to help you, just type in google – k2 seo tips and tricks
I read a lot of interesting articles here.
Probably you spend a lot of time writing, i know how to save you a lot of time,
there is an online tool that creates unique, SEO friendly articles in minutes, just type in google – laranitas free content source