Polymorphic Associations in Rails Explained

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

 

Image from Office guides for 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
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
add_index :pictures, [:imageable_type, :imageable_id]
While we are at it, we can also limit the size of 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 option
add_column :pictures, :imageable_type, :string, 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.

Leave a Reply

Your email address will not be published. Required fields are marked *