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!