Sinatra CRUD Application build and learning more about Join tables

Most of us have some sort of guilty pleasure on our Netflix. Maybe it’s true crime stories, maybe its really trashy reality shows, but for my family — it’s Ghost Adventures. My family is outright obsessed with the wacky stories presented on ghost adventures, so I decided it might be fun and interesting to build an app where users can submit hauntings, track how many ghost sightings they’ve had, and compare the amount of sightings they’ve had with other users for some friendly competition. This overview will not be a full tutorial for the application (though if you’d like one in the future, let me know in the comments!) but rather it will discuss a principal concept behind the build — using ActiveRecord and join tables, and how I fixed the setup of my join tables to accomplish my primary goals.

A photo of the finished application

The Build

It was my idea that multiple users should be able to say they’ve been to the same spot that caused the most trouble for me during this project. For example, if one user submits the Ryman Auditorium as a haunted spot, then I believed another user should also be able to say they say a ghost at the Ryman Auditorium, without creating another haunting object for the Ryman, because then the views on this application could get repetitive fast and just be filled with many different users corroborating they went to the same place and saw the same ghost. I wanted to have one sighting, that could say “sighed by x amount of users.”

Originally, when I set up my project, I set up three classes. The City class, which held city objects with a name and state. The Ghost class, which was an object with a name and content that would basically serve as the haunting/sighting information. And of course, a User class with a name, email, and password. I originally used the ghosts table as my join table, the code as follows:

class CreateGhosts < ActiveRecord::Migration
def change
create_table :ghosts do |t|
t.string :name
t.string :content
t.integer :city_id
t.integer :user_id
t.timestamps null: false
end
end
end

City_id and user_id acted as my foreign keys, and the Ghost model class was set up with belongs_to relationships through Active Record.

class Ghost < ActiveRecord::Base
belongs_to :user
belongs_to :city
end

This code worked in I was able to create ghosts/hauntings that happened in a city such as Nashville or Atlanta and a User object would be associated with the ghost so that user could edit or delete their ghost/haunting. However, this setup isn’t conducive to my idea that a ghost should have multiple users that could claim it as sighted, I would need to make a separate join table and stop using my ghosts table as a join, I needed another table that joined the three classes, something I hadn’t done before. After research and consulting, I found making a join table joining three classes was the same as making a join table joining two classes. it just required three foreign keys. After some experimentation, this is what I came up with for my join table:

class CreateUserGhosts < ActiveRecord::Migration
def change
create_table :user_ghosts do |t|
t.integer :user_id
t.integer :ghost_id
t.integer :city_id
t.timestamps null: false
end
end
end

This would allow me to create a relationship in the Ghost model that the object has many users through user_ghosts. The ghost table I changed to be:

class CreateGhosts < ActiveRecord::Migration
def change
create_table :ghosts do |t|
t.string :name
t.string :content
t.string :user_id
t.timestamps null: false
end
end
end

I kept a foreign key for user_id in the ghosts table, so user and users could be separate in a ghost object. The user will be the creator of the ghost, what the ghost belongs to and capable of editing and deleting the ghost. The users a ghost has many through user_ghosts, are associated with the ghost but should not have the capability to edit or delete these ghost objects if they weren’t the creator, if they just sighted them.

class Ghost < ActiveRecord::Base
has_many :user_ghosts
has_many :users, through: :user_ghosts
has_many :cities, through: :user_ghosts
belongs_to :user
end

My ghost model exemplifies this differentiation, and this difference is what drives the ability of my app to allow for many users to say they have sighted the same ghost object, without many users trying to change the name or content, which would become confusing fast.

Conclusions

Overall, this project was a lesson in learning more about how database tables work and how ActiveRecord relationships can lead to some really incredible functionality. I learned not to overthink things too much, because making a join table between three classes seemed so daunting to me, and then I realized the difference was really just one more foreign key. This is so simple, and I wonder why I ever felt it was daunting to begin with. However that’s part of the journey, overcoming those daunting ideas and making them into code. If something seems too complex to you, I hope you remember each bit of code knowledge can be built upon, and if you know how to join two things, you know how to join more. If you know how to build one class, you can build more and so one. Thank you for reading and I hope this message was encouraging, and if any of you want to see more of the code from this project, please check out the repository. If any of you are interested in a full tutorial or have any other requests/suggestions, leave a comment or contact me through my Linktree!

DEMO

If you are interested in seeing the project at work, here is a video demo of the application.

Audio engineer with 8+ years of working with Grammy-winning teams, full-stack engineer specializing in React/Redux and Javascript. https://linktr.ee/hopegipson

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store