Understanding Polymorphic Associations in Rails

nissrinecan

Nissrine Canina

Posted on October 1, 2022

Understanding Polymorphic Associations in Rails

The Ruby on Rails documentation defines polymorphic associations (PA) as: "A slightly more advanced twist on associations is the polymorphic association. With polymorphic associations, a model can belong to more than one other model, on a single association.". As I am researching this topic to implement it in my project, I have found that understanding what problems polymorphic associations solve makes learning this new concept much simpler.

Another way to rephrase the Rails documentation definition is that PA is needed when multiple parent models use one child model. To understand how useful PA is in this case, we should investigate the issues that will arise without applying PAs. For demonstration purposes, let's create a simple Rails application with four models using conventional has_many and belongs_to associations.

has_many and belongs_to associations of article, post, event, and comment models

Then, let's check our comments in the schema:

comments schema

As we can see in the comments schema, event_id, post_id, and article_id are required even though the comment is meant to be assigned for one model at a time and not all in one instance. Let's hop on our console and see the flow of events:

creating post comments in the console

Note that the comment did not save to the database because it requires the existence of the event and the article (recall the null: false property in the schema which means that all foreign keys must exist in order to create and save a comment to the database). There is a way around this issue even though it is definitely inefficient:

creating a post comment with event and article ids in the console

It is clear how we had to build new columns in our comment table for the event and article when the main purpose was to create a comment for the post only. We can see how conventional associations were not helping the DRY principle (unnecessary rows in tables with null values) in this case. We can remove the constraint null: false and add optional:true in the comment class. This will allow us to create and save a comment successfully without manually assigning the unwanted foreign keys; however, they will be still parsed as nil in the comment table.

The efficient solution for this example is PA. Let's update the migration to add PA and remove all the foreign keys for the article, event, and post models.

migration add polymorphic to comments and remove foreign keys

Then, we update all the models as shown below:

add polymorphic association
article as commentable
event as commentable
post as commentable

The :commentable refers to the post, event, and article models. Let's comment on a post and an event in the console and pay close attention to commentable_type and commentable_id:

create a comment on a post using polymorphic associations

Here we are creating a comment on a post using PA without wasting space in the database. We also follow the DRY principle by removing three belongs_to associations from the comments model. Also, we are able to link the behavior expected for a commentable component to any model. We are separating concerns and reusing code. Let's create another comment on an event and note the change in the commentable-type:

create a comment on the event using polymorphic associations

We can see that the comments were created without loading our tables with nil columns. The commentable_type refers to the target model that is associated with the comment. Therefore, only the target model foreign key is created.
The comments are accessible using has_many association:

accessing post and event comments

It is important to know that accessing the parent model of a specific comment will require the use of .commentable method. For instance we cannot use comment.post or comment.event to access the parent model otherwise we will get a NoMethodError. Remember that we modified the belongs_to associations with as: :commentable. Where commentable refers to the parents' models. Check the console below:

access the parent model of a specific comment

access the parent model of a specific comment

Finally, we can think of PA anytime we are faced with the need to share a model property among other models. For instance, an image model can be used by other models such as products and categories. so, imageable is the way to go!

I hope this walkthrough example is helpful for anyone learning PA for the first time.

💖 💪 🙅 🚩
nissrinecan
Nissrine Canina

Posted on October 1, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related