Polymorphic Associations in Ruby on Rails

Polymorphism is a fancy term often used in software engineering. It can be a fun one to throw around in meetings to sound smart, but make sure you really understand what it is and why you would want to use it.

When you define an association with Active Record, we usually assume that one model connects to another and that's it.

You've probably seen this before, it would look something like this:

class Image < ApplicationRecord
  belongs_to :profile
end

class Profile < ApplicationRecord
  has_many :images
end

But a polymorphic association allows a model to belong to multiple other models. In this case, we might create a Profile model and a BlogPost model, and both have many references to Images.

class Image < ApplicationRecord
  belongs_to :imageable, polymorphic: true
end

class Profile < ApplicationRecord
  has_many :images, as: :imageable
end

class BlogPost < ApplicationRecord
  has_many :images, as: :imageable
end

Recap of How Model Associations Work By Default

With a normal association between 2 models, each model defines its role in the association.

Ultimately, one table has to store an ID that points to the other and that's what our belongs_to and has_many calls are laying out. The model with the belongs_to defined will store the id to the other model being referenced.

class Image < ApplicationRecord
  belongs_to :profile
end

class Profile < ApplicationRecord
  has_many :images
end

Here we have an Image model that belongs to a Profile, and so our images table in the database will have a profile_id field to store the profile it belongs to. Additionally, our Profile model might list this association as a has_many meaning there could be multiple images that point to the same profile.

But images are a pretty common part of any website or service and odds are we are going to have more models that also reference images.

This is where polymorphic associations come into play.


How to Build a Polymorphic Association

When we know we want a model to be associated with multiple other models we need to tell our shared model that it is polymorphic.

Our model will have to slightly change to account for this. Instead of just saying belongs_to :profile on our Image model, we have to list this association as a polymorphic reference (using polymorphic: true).

Instead of our table pointing directly to a Profile or BlogPost where our image would store a profile_id or blog_post_id we have to store a shared id that could point to either table. The Rails convention is to name this reference using the class name and adding "-able". Our Image class would name its polymorphic reference :imageable.

It should look something like this:

class Image < ApplicationRecord
  belongs_to :imageable, polymorphic: true
end

And then since our Images belong to our :imageable join table, our Profile model needs to slightly modify its reference like this:

class Profile < ApplicationRecord
  has_many :images, as: :imageable
end

But now we have a polymorphic model and we can introduce as many other shared models as we want.

If we want to create a new BlogPost model that also has images, we can do that very easily:

class BlogPost < ApplicationRecord
  has_many :images, as: :imageable
end

Altogether our models could look something like this:

class Image < ApplicationRecord
  belongs_to :imageable, polymorphic: true
end

class Profile < ApplicationRecord
  has_many :images, as: :imageable
end

class BlogPost < ApplicationRecord
  has_many :images, as: :imageable
end

Writing Polymorphic Association Migrations

When we want to create a polymorphic table, we need to change a few things in our migrations.

Using the example above, our Image model can belong to either a Profile or a BlogPost. Instead of storing a profile_id or blog_post_id we store an id that can point to either called imageable_id. In order to determine which id the model points to we reference the imageable_type.

Here's an example migration:

class CreateImages < ActiveRecord::Migration[6.0]
  def change
    create_table :images do |t|
      t.string  :alt_text
      t.bigint  :imageable_id
      t.string  :imageable_type
    end

    add_index :images, [:imageable_type, :imageable_id]
  end
end

You can also use the references shorthand to keep things easier.

class CreateImages < ActiveRecord::Migration[6.0]
  def change
    create_table :images do |t|
      t.string :alt_text
      t.references :imageable, polymorphic: true
    end
  end
end

Adding a Polymorphic Column to an Existing Table

If you already created the table and just need to add a column, you can use the add_reference method.

Here's it would look:

class AddImageableToImages < ActiveRecord::Migration
  def change
    add_reference :images, :imageable, polymorphic: true, index: true
  end
end

Wrapping Up

Polymorphic associations sound more complicated than they are.

In reality the only real change is that our migration and model references need to include the polymorphic: true flag.

Besides that, name the shared reference using the "-able" naming convention and Rails will handle everything else!

Featured
Level up faster
One on Ones: 101
Leveraging Other People's Experience
Business ideas
Check out my list.
Hey, I'm Nicholas Dill.

I help people become better software developers with daily tips, tricks, and advice.

Level up
Related Articles
More like this
Writing Migrations for Polymorphic Associations in Ruby on Rails
How to Manage Null Constraints With Migrations in Ruby on Rails
How to Add Custom Filters to Administrate Dashboards