Try โ€‚โ€‰HackMD

Polymorphic Associations

tags: Polymorphic, Association, Sequelize, SQLAlchemy, One-to-Many Many-to-Many

Note: This page is for a high level overview of polymorphic associations. If you've read this already or know that you need to set up some polymorphic associations in your project, there are links (currently only SQLAlchemy) at the bottom.

Where do I start?

Step 1: Do I even need this?

Something to consider before you jump into this guide is whether or not you even need a polymorphic association. This type of association is extremely handy for allowing a table to be associated with many other tables through the same system. However, it would be absolute overkill if your app doesn't need that kind of flexibility.

Are you adding comments on posts? Great! That's a fun feature, and doesn't need a polymorphic association to tie them together. Are we allowing comments on posts and also nested comments? Okay, now a polymorphic association can shine.

What about images? Can posts have multiple images? Are we allowing users to put 1 image on either a comment or a post? Both are cool, but neither requires a polymorphic association (if a post/comment only gets one image, you could always put the image url on the Post/Comment model). However, if both posts and comments can have multiple photos, a polymorphic association can clean up your database a bit.

Step 2: I still don't know if I need it.

Let's take a look at a previously mentioned example: an application where you can add multiple images to either a post or a comment. If we didn't want to use a polymorphic association, we could do this in one of two ways:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More โ†’

In this first example, we're putting two foreign keys on the Images table, but we'll only ever use one. That means we have to allow both of those keys to be null, so that an image can be either associated with a post or a comment. This is not great design, and if we ever decided we wanted to be able to add an image to something else (let's say the user's profile), we'd have to add another foreign key to Images for that!

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More โ†’

In this second example, we rid ourselves of the need for those weird null foreign keys, but now we have an extra table! Again, not great design.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More โ†’

Here we have the polymorphic association. It's a bit strange to look at, because where is the imageable_type coming into play? But that will be vital in the code. The imageable_id will refer to the primary key of another table, as we're used to by now. The imageable_type, however, will be used to say which model we're connecting this Image to, whether it be a Post or a Comment. So the combination of imageable_id and imageable_type will be used to pinpoint both which table and which row in that table we're connecting this to.

Note: if you're looking through resources on the internet for information on polymorphic associations, you may find examples with attributes like commentable_id & commentable_type or imageable_id & imageable_type. I used imageable_id and imageable_type here. It really just means that if a Post can be connected to a Images table through the imageable_id & imageable_type attributes, then it's "imageable"!

Note 2: I've used an enum for the imageable_type in the polymorphic association. You could also use a string. The tradeoff that you get is a little less setup when using a string in exchange for less protection on the values that could be used for imageable_type. We'll be using enums in the tutorials I've linked below, but if you decide to use a string datatype instead you can just ignore enum-specific changes to files.

Step 3: Okay, I'm sold. But can I get an example with code?

Absolutely! Here are the links to simple walkthrough examples of polymorphic associations with a few different ORMs

Backend Setup Walkthrough
Flask/SQLAlchemy
Express/Sequelize
Rails/Active Record Future Addition