Many-to-many relationships with Ruby on Rails

Photo by BP Miller on Unsplash

Many-to-many relationships with Ruby on Rails

A database how-to checklist for the relationship workhorse

It's everywhere!

This is a relationship model that comes up frequently, so this is a quick rundown of how to build out a representation of this database model from scratch. This blog post is intended to be a step by step guide to implementing your own many to many relationship in Rails, and should be an excellent jumping-off point for anyone who's new to Rails. Follow along, and happy coding!

Boilerplate - create your framework

$rails new boardgame-api --api --minimal

We'll frame out a relationship that describes boardgames and their mechanics. As one might expect (given the title of the post), this will be a model in which each boardgame may have many mechanics, and each mechanic may belong to many boardgames. This is a type of relationship cardinality that is very common, and crops up pretty readily when thoroughly fleshing out just about any object relational model. This is often also illustrated with examples that range from things like books and authors, teams and players, actors and roles - the list goes on! Recognizing many-to-many relationships and viewing the model through the correct lens can help get you to working on the fun parts of your project.

Below is an example "entity relationship diagram" that describes the relationships between the entities of "Boardgames", "Mechanics", and "MechanicRecords" (the join table we'll use to facilitate this many-to-many relationship). Intuitively, a boardgame may in fact have many different mechanics - or perhaps only one or two! Since we can't know the exact number of mechanics that may apply to any given boardgame, we could do something like give every boardgame record twelve mechanic slots, use only the mechanic slots we need, and then leave the rest as nil values - but that would be messy and unsatisfying! In order to account for an unknowable number of relations to be built between "Boardgames" and "Mechanics", we'll use the join table "MechanicRecords" to create a record for each of these relations in a single location.

ERD.jpg

The entity relationship diagram shows that each "Boardgames" and "Mechanics" have their own properties that they might be concerned with storing, and that the "BoardgameMechanics" table will (at least in this example) mostly be concerned with keeping a foreign key from each of the associated classes - this is visible where the 'id' attribute from "Boardgames" and the 'id' attribute from "Mechanics" link up to the respective "boardgame_id" and "mechanic_id" attributes on the join table.

Create your models and migrations

  • $rails generate model CreateBoardgames name msrp:integer
  • $rails generate model CreateMechanics name
  • $rails generate model CreateMechanicRecords boardgame_id:integer mechanic_id:integer

Rails is pretty generous here, and helps us by creating both the models for the classes (in the /app/models/ directory), and also the migrations (in the /app/db/ directory). Even better: the migration files already have the table properties that were specified when the rails model generator was run! (Note: the datatype for each property in the migration tables defaults to string, otherwise the type must be specified after the property is listed.)

generate_models.jpg

One of the double-edged swords of Rails is certainly the power of generators - they do a lot under the hood, and are super helpful! Still, there are always dangers with abstracting away too much and losing an understanding of what sorts of things are being taken care of for you behind the scenes. In this case, you can see that the rails model generators have created the necessary migrations and models to represent this particular relationship. This can be taken even further with generators, and it's surprisingly easy to create whole resource routes with rails g resource <resource info>. Try to keep in mind which elements of the MVC model are being handled for you, as a lot of troubleshooting requires you to understand the flow of the request/response cycle as it relates to the structure of your program.

Add the associations

The final step is simply adding the relationships between each of the classes. This is done in the Model file, where we define each class.

add_associations.jpg

Oh right - I promised a checklist!

And that's it! Your database now models one of the bread-and-butter relationships, and is all ready to migrate and begin saving all of your many-to-many relationships. This is an extremely common pattern that comes up in relationship modeling, so boiling it down to a quick check-list can help you move on to your next task. That mental checklist can be boiled down to:

[] - Create the framework

[] - Add the models and migrations

[] - Add the associations

[] - Hit that delicious migrate

Enjoy!