Step by Step Guide to Polymorphic Associations in Rails

What are polymorphic associations?

Karim Butt
3 min readJan 20, 2015

--

Typically, in Rails, when you need to make one class belong to multiple classes, you have to add a separate belongs_to method in the class each time. For example, if we think about the domain model of a generic social media web application, one can imagine that a comment can be made on a photo, a wall post, an event, an article, etc.

If we were to model this without polymorphic associations, we could do one of two things:

  1. Create a single comments model with multiple belongs_to for each association:
class Comment < ActiveRecord::Base
belongs_to :picture
belongs_to :post
belongs_to :event
belongs_to :article
end

The problem with this method is that your table will have a number of foreign keys for each of the `belongs_to`; however, only one of them will actually have a value at one time — a comment can only belong to one of these other models at one time.

2. Create multiple different comments models:

class PictureComment < ActiveRecord::Base
belongs_to :picture
end
class PostComment < ActiveRecord::Base
belongs_to :post
end
class EventComment < ActiveRecord::Base
belongs_to :event
end
class ArticleComment < ActiveRecord::Base
belongs_to :article
end

Going this route, we no longer have the issue where we have a number of unused foreign keys; however, the issue is that there are now a lot of models that all more or less do the same thing and act the same way.

The solution to this is to use polymorphic associations with one model — this is a model can belong to more than one other model, on a single association.

How to create a polymorphic association

In order to create a polymorphic association, we create a comments model; however, rather than making it belong to each of the other classes, we make it belong_to a generic commentable object.

class Comment < ActiveRecord::Base
belong_to :commentable, :polymorphic => true
end

Now, using this method, we can make any number of models “commentable” — meaning that they can be commented on. All we have to do is make each class have many comments:

class Picture < ActiveRecord::Base
has_many :comments, as: commentable
end
class Post < ActiveRecord::Base
has_many :comments, as: commentable
end
class Event < ActiveRecord::Base
has_many :comments, as: commentable
end
class Article < ActiveRecord::Base
has_many :comments, as: commentable
end

A helpful tip

Using the polymorphic association method above, we now have only one comments model and our code is pretty DRY. However, the controller could still use some DRYing up. For example, when we’re looking to create a particular comment, we have to find the appropriate commentable to build the new comment with:

class CommentsController < ApplicationController
def create
if params[:picture_id]
@commentable = Picture.find(params[:picture_id])
elsif params[:post_id]
@commentable = Post.find(params[:post_id])
elsif params[:event_id]
@commentable = Event.find(params[:event_id])
elsif params[:article_id]
@commentable = Article.find(params[:article_id])
end
@comment = @commentable.comments.build(comment_params)
end
end

You can imagine that every time you need to find the commentable that the comment is associated with, you would have to run all of that conditional logic. However, Ryan Bates’ Railcast on polymorphic associations has a really nice method that can be added to DRY up your code in the following manner:

class CommentsController < ApplicationController 
def create
@commentable = find_commentable
@comment = @commentable.comments.build(comment_params)
end
privatedef find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
end

This is a really nice piece of Ruby programming that uses regex to look through the params and find any params that end in “_id”. It then takes those params, uses the classify method to format the string to look like a class. It then uses the constantize method to find a declared constant with the name specified in the string. Finally, it looks through the class for the ID value in the key/value params hash.

--

--

Karim Butt

Engineering and product leader. Founder of GlossGenius. Write about engineering, tech, productivity, management. Working on something new.