Polymorphic Associations allow you to create a single model and let it “belong to” multiple models. Allow me to explain how they work.
I wanted to have a single Photo
model, which can be used for thumbnails, employees, product, etc. for the practice management app that I am working on.
I knew that making a different model for each of these is not feasible, and it will lead to a lot of duplicated code, so I implemented them using Polymorphic associations.
Rails Polymorphic Associations
Well, with polymorphic associations, a model can belong to more than one other model, on a single association.
For example, you might have a picture model that belongs to either an employee model or a product model (visualized in the pic above). Don’t worry if you don’t understand the code written there. You will after reading this article (probably).
The most weird part of the above code seems to be the part which says
belongs_to :imageable, polymorphic: true |
This just means that the model Picture does not belong to any of the Employee or Product model but to something called imageable
.
Uh, oh. I was taught that models belong to models and nothing else. You can check any code which has belongs_to clause.
Well, that is kinda true, but we have to change it a little while using polymorphic associations (if you forgot, they mean that it could be associated with many models). So, imageable here acts as an interface to link to, for other models which link the same way they did except now they add a clause of as: :imageable
. For example, the Person model now looks like:
class Person < ActiveRecord::Base has_many :pictures, as: :imageable end |
You could have used any word in place of imageable, like picturable, photogenic, photograph, baklind, dhoni anything! They just have to match your column name.
Wait, what? Which column? What column? Which table?
Good question. You have to setup a reference in your Picture model to store the foreign key of the row it belongs_to
. Now how do we do that?
First, let us look at how we did for non-polymorphic associations. If, for example we have a Question model and Choice model, we say choice belongs to question and question has many choices. So, to make that thing work properly, we generally include a column question_id
in the choices
table. That thing generally works out without any problem and we live a happy satisfied life.
But, there is no specific model assigned here in the polymorphic associations. What field do we use then? A wise man said, double the amount if you are not sure. Lets follow that wise man. We use two foreign keys! One key for the id
and another for class
.
For the Picture case, they foreign keys are imageable_id
and imageable_type
. The imageable_id
is the id of the record this row belongs to and imageable_type
is the class name. The migration looks something like this,
add_column :pictures, :imageable_id, :integer add_column :pictures, :imageable_type, :string |
or you can use a handy trick provided by rails,
t.references :imageable, polymorphic: true |
Just run the migration now, add the snippets to your model, and congratulations. You successfully have configured a polymorphic association!
Yayy!! Well that was simple. Thanks man. Now, how do I actually reference it?
Ahh, the easy part. Let me assume that you know how to get data from model ( person = Person.find(23) ). There are two ways to create a reference, from the parent side or from child side.
person.picture = picture |
or
picture.imageable = person |
That is all really is there to assigning. Don’t forget to call save method on picture though, otherwise its gone as soon as the object stops persisting.
Seems simple enough, but I have a question. How do you use it to accept data from forms?
This is tricky and so I think I should leave it to experts. Check out this Railscasts. It shows how to do all this for comments. Ideal if you wish to create Facebook and would need comments for all your Photo, Status, Video models.
You can either use the method shown by Ryan Bates in Railscasts or do manual assigning the way I showed. I prefer doing the manual assigning though. Easier to setup and less confusing. Plus, you have full control as you know where the code is and what it does.
I have one little trick which I’d like to share. As many people know, adding index to column in tables makes it for faster searching for data through that column, you all will be tempted to add index to both imageable_type
and imageable_id
columns by typing two migrations like this
add_index :pictures, :imageable_id add_index :pictures, :imageable_type |
add_index :pictures, [:imageable_type, :imageable_id] |
imageable_type
column. We are storing it as a string but do you think we would really have a model name of 255 characters? Decreasing the size will also allow for smaller indexing and faster searching. We can limit the size by passing an optionadd_column :pictures, :imageable_type, :string, limit: 15 |
Have some of your little tricks or you think I did a mistake and a proved to be a bad teacher? Write it in the comments.