Ruby on Rails
SimplePolymorphicAssociation

This is Only Available in Edge Rails: Currently (1.1)

I was having troubles using the new Edge Rails feature Polymorphic Associations, but besquared was nice enough to give me a run down on his method and he saved me probably entire days or weeks worth of effort on the subject in the #Rubyonrails IRC channel.

I’ve taken his example (see HowToUsePolymorphicAssociations by besquared) and dumbed it down for us folks who need a simple relationship between one table and several tables in a one to many type relationship. Many to many is not always required when dealing with Polymorphic associations, so I thought I would bring you a simpler example.

For the purpose of this demonstration I’ve come up with a small example scenario. Lets say you have created a blogger application but it also houses a photo gallery and you wish for your comments to allow many threads for each item posted, be it an image or post in your blog.

Once you click into a thread you want to be able to view the thread but also be still able to click back on a link to take you around to the place you were again.

The problem that you’re having is that you can find a way for the Thread model to reference either Image or Post, but not both.

To solve this problem firstly create four database tables with the following values:

<pre> POSTS id title VARCHAR content TEXT

<pre> IMAGES id title VARCHAR image_data BLOB

<pre> THREADS id discussable_id INT discussable_type VARCHAR title VARCHAR

<pre> REPLIES id thread_id INT content TEXT </pre>

The threads table is the only one that should be any surprise to you, it has two values of noteworthy importance.

discussable_id – This will map to the id of either a post or an image in our model. Must end in _id and not have a name relating to any specific existing model.

discussable_type – Will tell the application which model we are accessing and must be at least as long as the longest model name you are planning on connecting to. In this case ‘Image’ is 5 characters in length. Must end in _type and uses the same name as _id.

Your model will look like this:

<pre> class Thread < ActiveRecord::Base belongs_to :discussable, :polymorphic => true has_many :replies, :dependent => true end

class Post < ActiveRecord::Base
has_many :threads, :as => :discussable, :dependent => true
end

class Image < ActiveRecord::Base
has_many :threads, :as => :discussable, :dependent => true
end

class Reply < ActiveRecord::Base
belongs_to :thread
end

With these models and before mentioned database you will now be able to access the information through a polymorphic association in Rails. The only thing not really necessary is :dependent => true. It is important to declare the name you are using in your table in the :as clause of your model in any place you would like to attach to the polymorphic model. Also you must put :polymorphic => true where the model has an uncertain amount of other models to attach itself to.

Everything you wanted to know about the item a thread is related to, can be found by executing the following:

<pre> @parent = Thread.find(params[:id]).discussable </pre>

The title of the Image or the Post can then be found with @parent.title and the id with @parent.id etc.

Well that’s how you do that, I know there is very little documentation out there about this. I hope this entry has helped some of you out as it took a little bit of time for me personally. I also hope I didn’t confuse anyone.

Have fun with this powerful new feature of Rails.


This tutorial uses an ActiveRecord inherited class named “Thread”. Thread is also a core class. Is this a potential problem?

Kind of a strange example, lets make it even easier.

Here’s the models:
Image has_many :tags
Comment has_many :tags
Tag belongs_to :image
Tag belongs_to :comment

What polymorphic associations are trying to solve is that, when you have multiple models all associated with the same model. That model then has all these belongs_to which doesn’t always make sense, e.g. if I add a Tag to an Image’s tags collection it’s not going to set the comment attribute of the Tag object.

So with polymorphic associations it looks like this:

Image has_many :tags, :as => :taggable
Comment has_many :tags, :as => :taggable
Tag belongs_to :taggable, :polymorphic => true

In the database the Image and Comment models map as they normally would:

images (id, name)
comments (id, body)

but the Tag maps differently:

tags (id, name, taggable_id, taggable_type)

taggable_id is the foreign key i.e. what the tag belongs_to e.g. it would be the primary key in the images table if the tag belongs_to an Image or the primary key in the comments table if the tag belongs_to a Comment.

taggable_type is the type of the object the Tag belongs_to e.g. Image or Comment. taggable_type is needed because the taggable_id column could have duplicates because it can reference a primary key in any table. So when rails does a select for a tag that belongs_to an Image it looks like this:

select id, name, taggable_id, taggable_type
from tags
where taggable_id = 1
and taggable_type = ‘Image’

or a Comment:

select id, name, taggable_id, taggable_type
from tags
where taggable_id = 1
and taggable_type = ‘Comment’

You see taggable_id is not enough for the where clause.

(The only question that remains is why would anyone want to do this instead of having two columns for the two references. In this example the Threads table having both Image_id and Post_id. Not only would this be simpler, it would also use much less space in the database (remember, you are storing the NAME of the class in each row of the Threads table!), allow the DBMS to enforce referential transparency and it would also be much much quicker! It’s cheaper to compare two integers (especially if you do have indexes on both Image_id and Post_id) than it is to compare an integer AND a string. The only thing you “gain” using Polymorphic Association is that you can reference only the Image or the Post. Which is easy to enforce if you’re using two columns and hard to get rid of once you find out you actually do want to allow both references.)

ManytoManyPolymorphicAssociations

——————

Wow, I’m the author of this original article. It’s been a little while and I had to come back and see how this was done again. Good lord, someone please re-write it so it’s useful! :)