Breadbook: an exploration of polymorphic relationships

Garrett Bodley
5 min readJan 26, 2021

For my third project at Flatiron, I decided that I wanted to expand what I built out with Sinatra. Ruby on Rails (or Rails for short) is the name of the framework we’ve been studying for the past month or so. Rails is quite an impressive piece of software, allowing you to generate the requisite views, controllers, and databases in short order. I found the process of revisiting a prior project with this new toolkit and my expanded knowledge to be engaging, motivating, and also very educational.

With Sourdough Tracker, users were not able to interact with each other. Each user’s ingredients, recipes, and bakes were separate and distinct, only to be seen by the individual user that owned them. With this project, I wanted to expand things to allow for basic social interaction between the accounts. I wanted users to be able to trade recipes or share their success with the flour from a specific mill or producer (I myself am a fan of the flour from Janie’s Mill). And it only follows that users should be able to leave comments sharing their own experience with a specific recipe or ingredient.

Mapping out the relationships

The first step to making this a reality was mapping out my models and their various relationships. The structure, the skeleton of your application, really arises from how you set up your database, and as such, I spent a good amount of time mulling over how everything should relate.

A drawn-out schema of my models and their relationships.

The somewhat confusing looking schema you see above is eventually what I settled on. There’s a lot going on, but you can see that some models needed to be public, accessible by any user, while I wanted other information to strictly belong to the user that created it. Importantly, I wanted users to be able to save ingredients or recipes they found useful so they could easily reference them without having to search through each ingredient or each recipe posted on the site.

Okay, I’ve done that before. This is a has_many to has_many relationship. All I need to do is set up a join table and tell ActiveRecord about the relationship and I’m good to go. Sure, I have to set up a users_ingredients table and a users_recipes table, but that’s not terrible.

Ugh. Wait…

Comments are also a join table between a user and an object, but comments can belong to a post and an ingredient and a recipe. And I also wanted to add the ability to “like” things, so each Like functions as a join table between a User and whatever object is liked?? At this point, I’m looking at multiple join tables for each object, which is starting to feel rather repetitive. There has to be an easier way, right?

Enter polymorphic associations

As tempting as it was, I did not end up creating an independent join table between each model and my users. Instead, I utilized a feature of Rails called polymorphic associations. Polymorphic associations allow you to create a join table that can relate to multiple classes, drastically simplifying your code and allowing for a degree of modularity that I found pretty nifty.

A normal join table has two columns, one for each model it is keeping track of. Each column is named after the model that it keeps track of and each row contains the various indices, each one a foreign key pulled from a different table. In short, with a regular join table, the column tells you which models are involved and the row tells you which specific instances are related.

With polymorphic associations, both the index and the type of associated model are stored in one row. The columns simply keep track of which is the index column and which is the model-type column.

image pulled from guides.rubyonrails.org/

This allowed me to create a Bookmark model that could relate a User with multiple objects without the need for a join table for each model relation!

The migration for my Bookmarks table is as follows:

create_table :bookmarks do |t|
t.belongs_to :user
t.bigint :bookmarkable_id
t.string :bookmarkable_type
t.timestamps
end

And the corresponding model relations:

class User < ApplicationRecordhas_many :bookmarksend
class Bookmark < ApplicationRecord

belongs_to :user
belongs_to :bookmarkable, polymorphic: true
end
class Recipe < ApplicationRecord  has_many :bookmarks, as: :bookmarkableend

Most exciting was that Users could both keep track of the recipes they created in addition to the recipes they wanted to “bookmark”. And this same model could let aUser “bookmark” posts, ingredients, or any future model added to the database!

This modularity made adding or removing features a relative breeze. Let's say I add the ability to upload images, and want users to be able to like any image. All I have to write is that an image has_many :likes, as: :likeable and I’m off to the races.

This proved particularly handy with the ability to abstract parts of your views into a partial. Once I wrote the code to display comments or to add a like button it was relatively easy to plop that down wherever it was needed since the object that kept track of everything was the same, and all the info was stored in the same database!

Behold the book of bread

I’m rather pleased with what polymorphic associations, and Rails in general, allowed me to build during this project. Returning to a previous project idea with the newfound skills and knowledge I’ve developed in the past month has been a wholly rewarding experience. It’s hard to believe I barely knew how to code back in October.

Below is a video overview of the website that I built. Feel free to check out the code on Github and please feel free to reach out if you have any questions or suggestions on ways I could improve!

--

--