Don’t worry, they really aren’t that scary, but it’s the sort of concept that slips away from you if you haven’t worked with it in a while. For a recent code challenge, I was asked to find a polymorphic use case for the upvotes table in my Graffito app. Currently, the table functions as a join between the user and graffiti models. There is a many-to-many relationship between users and graffiti and upvotes is the glue that binds them. The association declarations and schema look like this:
This is fine so long as we only want to allow users to upvote one model, Graffiti. But what if we expand that feature to favorite specific photo uploads that belong to a graffiti? At first glance, it might appear that a new upvotes tables is required: upload_upvotes. But that instinctually feels like overkill and a violation of the DRY principle.
How can we extend the upvotes table to handle relationships to not only graffiti but also uploads? Polymorphic Associations to the rescue.
According to Rails docs, “with polymorphic associations, a model can belong to more than one other model, on a single association.” This is achieved by abstracting the association naming more broadly, often just by adding able to the join model name, i.e., upvotable. Let’s take a look at the new associations and schema:
On line 16 above, we establish a polymorphic association, upvotable, which translates to two new columns in the upvotes table, upvotable_id and upvotable_type. Upvotes can now belong to more than one model on the single association; upvotable functions as an alias to any other model we attach it to, e.g., graffiti and uploads.
There’s one more thing I want to unpack. A user has many upvotes. But upvotes now attached to multiple models so we have to devise away to query the graffiti or uploads that users have upvoted. Once again we can take advantage of our polymorphic association and simply specifiy that source types we’re interested in (Graffiti and Uploads).
The great thing about polymorphic associations is that they scale really well. Imagine we decide to add upvotes to comments, drawings, or videos. That’s not a problem. The upvotes table can be extended to handle any number of models on our single abstract association, upvotable, and we don’t have to worry about managing a bunch of other upvote tables. Polymorphic associations are efficient and elegant if not a little confusing. They are well worth the effort.