Skip to content

Instantly share code, notes, and snippets.

@clamstew
Last active February 18, 2022 13:18
Show Gist options
  • Save clamstew/9282155 to your computer and use it in GitHub Desktop.
Save clamstew/9282155 to your computer and use it in GitHub Desktop.
A blog post on polymorphic associations. By @clamstew and @truffaut

Why Polymorphic Associations:

This was a question from @photonerddan during class, and after looking through many examples online approaching it from an inheritance stance, as well as ones with examples that seemed outside of common use-cases, Mike and I decided to approach it from a problem / solution standpoint, which looks at it from a database perspective.

We will cover it more in-depth during the second week of Rails MakerSquare, but for now here is a short example to highlight the problem that polymorphism is solving in your database.

So let's start with the problem it is solving:

Polymorphism keeps you from having excessive foreign key columns in a database table where multiple things, such as notification feed items, are owned by other objects.

**Polymorphism helps solve the problem of creating a

  • talk about the issue of multiple inheritance on the model side
  • talk about doing it without

The problem is your trying to create an association with mulitple inheritance.

For example you want ot have a relationship between users and posts, photos, and other items, through notificaitons.

The initial solution that

If you ever have a database table where you are using mulitple columns for the purpose of providing a foreign key to a common shared object amongst those multiple columns, ... then there is a solid chance that you have a fragile database structure. Your database table might look like this:

(this)


The Problem:

Say you have a notifications feature for a user like on popular social networking sites, where it pulls in all types of media:

You might have tables for photos, videos, and posts, and of course you have a users who own all the things in these tables. Then if you want to have notifications for all those items when, for example they are created, you have a notifications table with a column telling you if a user has seen that notification.

users table:

id username
1 slaney98
2 jokester71
3 mzuckdog
4 yoyoyoyo1

The user_id would be a foreign key in this instance back to the user who posted the photo, video, and post, respectively.

photos table:

id photo_url user_id
1 http://s3.amazon.com/alsdkjfalksdjflsiew9eufiua 3
2 http://s3.amazon.com/vxvfasdfwe34rvbbblnkoptemr 1
3 http://s3.amazon.com/mmbmbafde33djflsiew9eusdfa 3
4 http://s3.amazon.com/tlsdkjfalkscsdfsdee9eufiua 4

videos table:

id video_shortcode user_id
1 GnO7D5UaDig 4
2 FFtKLISfPE 4
3 vM40X2cvQ 2
4 TGLxjppFqeA 1

posts table:

id content user_id
1 "This is a post" 3
2 "This is also a post" 4
3 "This is a post too?" 1
4 "This is the coolest social network!!" 1

Notification Feature:

If I wanted to add a notifications table without Polymorphic associations, then my table structure would end up looking something like:

id feedable_type photo_id video_id post_id ...
1 photo 2 ...
2 video 4 ...
3 post 1 ...
4 video 1 ...

If you wanted to add another media type, then this structure becomes more brittle. You would keep adding columns, and it doesn't seem like an elegant solution or concise form of data storage.

You would want that notification record to have a foreign key of photo_id, video_id, and post_id. Depending on the amount of media types this could be a lot of extra foriegn key columns.


DRY up notifications with polymorphic associations:

Lets take a look at what we can do to make the notifications table more malleable to different media types, as well as use a more scalable data storage technique with less waste.

The general pattern should be to start with the model that will be shared between the other models -- in this case, notifications. We'll first set the notification model to belong to notifiable, and then our other models (picture, video, and post) will then all has_many :notifications, as: :notifiable, which is essentially multiple inheritance.

This code is illustrated here:

# app/models/notification.rb
class Notification < ActiveRecord::Base
  belongs_to :notifiable, polymorphic: true
end

# app/models/picture.rb
class Picture < ActiveRecord::Base
  has_many :notifications, as: :notifiable
end
 
# app/models/video.rb
class Video < ActiveRecord::Base
  has_many :notifications, as: :notifiable
end

# app/models/post.rb
class Post < ActiveRecord::Base
  has_many :notifications, as: :notifiable
end

Now, your notifications table data structure can be much simpler. You can consolidate the first part of the name of each foreign key into the notifiable_type column. Then notifiable_id holds the id of the foreign key in concept.

id notifiable_type notifiable_id user_checked
1 photo 2 true
2 video 4 true
3 post 1 false
4 video 1 true

An ounce of dry code is worth hours of work and many fewer headaches later. We hope this post can help clarify Polymorphic associations by looking at the database problem it solves.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment