Understanding Polymorphic Associations in Rails
Tags: ruby, rails, tutorial, webdev
Published: 2022-10-01 15:59:56

 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f8m0p7iar58accocadpf.png)
 
Then, let's check our comments in the schema:

![comments schema](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c2tt0jydy12janljilzh.png)

 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3bjic11gs8hblkbw8qov.png)

 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bsrcfp85pg9uxv9antfg.png)

 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bjjxsro0zb678ublg4y6.png)

Then, we update all the models as shown below:

![add polymorphic association](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5cyhjw53z4474iv8qsap.png)
![article as commentable](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6fc4bs0gb3nkf35jdi1p.png)
![event as commentable](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v4nqxpyl1df5vm1okhbp.png)
![post as commentable](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tcmhdnq7n6uovr9has4a.png)

 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zy2pcsokywkpif8o5nvq.png)

 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d6b7kf9eju1mj6fvbuwf.png)

 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r720mdg2sir5lp96fpkz.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sbye5t8mbmlrnryhrk7u.png)

![access the parent model of a specific comment](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6fpbgzv7k9xg52hvy8d7.png) 

 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.