cult3

Creating Polymorphic relationships in Ruby on Rails

Jan 13, 2016

Table of contents:

  1. The situation we are building for
  2. Creating the Model and the Migration
  3. Defining the Associations
  4. Conclusion

Polymorphic relationships are probably a topic you wouldn’t give a second thought to unless you really needed that exact type of functionality.

A Polymorphic relationship is where a model can belong to more than one other model on a single association.

The classic example of this is where you have many things that can be commented on, for example, a project, a task, or an attachment.

In each case, a comment is just a comment and so it makes sense to have only a single Comment model.

Fortunately for us, Rails makes building Polymorphic functionality really easy!

In today’s tutorial we will be looking at setting up polymorphic associations.

The situation we are building for

I briefly mentioned the situation that we will be looking at in the introduction to this post, but just so that we’re all clear, I’ll describe it in more detail here.

Imagine we are building a Project Management application that allows the user to manage her projects, tasks, and attachments.

This is a collaborative application, and so the user’s colleagues should be able to comment on projects, tasks, and attachments.

This means we need to have a Comment model, but the Project, Task, and the Attachment models all have the same concept of being “commentable” so we need to associate comments with three different types of model!

This is the perfect scenario for a Polymorphic relationship.

A Polymorphic relationship is where a model can belong to more than one model on a single association.

In this case, the comment will belong to something, but it doesn’t really matter if it is a project, a task, or an attachment.

We also have a nice and simple API for “commenting” on any of the “commentable” models.

So hopefully that makes sense.

If you have never had to build this functionality it can seem a bit random, but once you face this situation you will immediately understand the need for this special type of association.

Creating the Model and the Migration

The first thing we need to do is to use the Rails generator to generate a model and a migration for the Comment model.

Run the following command in Terminal:

bin/rails g model Comment body:text commentable:references{polymorphic}

This will create the following migration:

class CreateComments < ActiveRecord::Migration
  def change
    create_table :comments do |t|
      t.text :body
      t.references :commentable, polymorphic: true, index: true

      t.timestamps null: false
    end
  end
end

The references definition will automatically create columns called commentable_type and commentable_id. You don’t need to worry about these columns as Rails will take care of them for you.

Defining the Associations

Now that we have the migration for the comments table in place we can start to associate the models to be “commentable”.

First we will take a quick look at the Comment class that was created by the Rails generator:

class Comment < ActiveRecord::Base
  belongs_to :commentable, polymorphic: true
end

As you can see, this class has a single polymorphic association. Whenever you have a particular comment instance, you don’t need to care about who it belongs to.

To get the parent model you can simply call the commentable relation:

comment = Comment.first
comment.commentable

Next you can add the association to each of the models that should be “commentable”

class Project < ActiveRecord::Base
  has_many :comments, as: :commentable
end

class Task < ActiveRecord::Base
  has_many :comments, as: :commentable
end

class Attachments < ActiveRecord::Base
  has_many :comments, as: :commentable
end

Now each of these models will have the comments association. You can add comments as you would with any regular Rails association:

project = Project.find(1)
project.comments << Comment.new(body: 'Hello world!')

task = Task.find(2)
task.comments << Comment.new(body: 'Hello world!')

attachment = Attachment.find(3)
attachment.comments << Comment.new(body: 'Hello world!')

Conclusion

You could probably spend a long time as a developer without ever coming across Polymorphic relationships.

But the day you are required to implement this kind of functionality will eventually come.

It’s a fantastic thing that Polymorphic relationships is already such a well known thing.

Rails makes it really easy to deal with this situation without having to do any kind of model association trickery to get it to work.

This makes defining and implementing Polymorphic relationships a breeze.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.