So, in past few days, I got requests from people to offer a Quiz type question at PollDaal
. Being a nice fellow as I am (Modesty at its best!), I listened to them and started looking for ways to do it elegantly in Rails 3. After searching for a bit here and there, I got to know about polymorphic associations could be my saviour. I had learned earlier what they were but that was that. I quickly forgot them and thus they never came in my mind. So, someone said, teaching stuff makes you remember it. Thus, here I present, Polymorphic Associations 101 (kinda).
First things first, what are 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 form a little twist 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
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! For the Picture case, they foreign keys are ‘imageable_id’ and ‘imageable_type’. 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
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
And now, you should have faster finds, shouldn’t you? Well no! What we did was create two different indexes in the pictures table but we are always going to search for them together, instead we should do
add_index :comments, [:commentable_type, :commentable_id]
While we are at it, we can also limit the size of commentable_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 option
:limit => 15
while migrating the database to get truly fast searching.
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.