DEV Community

Jess
Jess

Posted on • Edited on

GameTracker: My CRUD MVC Sinatra App for Flatiron School

(Note: I'm moving my posts from my time at Flatiron School from my Github to this platform. This blog entry was first posted on January 22, 2020)

For my Sinatra project I made an app called GameTracker. I’ve been wanting to make an app where I could track my huge backlog of games across platforms for a long time. I wanted to make it so I could track how long a game takes to complete, add a notes section to keep track of completed quests and whatnot, a “What Should I Play Next?” section that would choose my next game based on certain criteria, etc. Unfortunately time didn’t allow for most of the features I wanted but I definitely intend to add them, either as a future curriculum or personal project.

gt-user-home.png

Features Included

  • sign up, log in, log out
  • /users page that shows every username/avatar and how many game titles each user owns
  • Private user pages (/users/:slug) that display the user’s game library
  • Create custom games
    • If a game already exists in the database with the same title it will return that game instead of creating the new one
  • Edit custom games where the title can only be edited if no user has the game in their library
  • Search for games using the IGDB API
    • Search results combine new and existing game data
  • Ability to add/remove games on a specific platform from your library from every view /games, /users/:slug, /games/:slug, /search/results
  • Confirmation messages for games being added/removed from your library or incorrect sign up/login information (using rack-flash3)
  • Ability to change username and password or delete account
  • /game/:slug show page with additional game information

Getting and Storing Information

I seeded the database with 162 platforms, 50 random users (excluding myself), and around 250 games from about 10 game title searches. The users were generated using a gem called random_username for the username and robohash for avatar generation.


To get the games and platforms I used the IGDB API. Each game search uses the API to retrieve information about all games that fit the game title query. Then, I parsed the data using JSON and created objects from that data the way I needed the information in order to add it to my database. From there I checked the database to see if the game already existed. If not, I created the game from the object. If the game had images, I created a GameImage and pushed that into game.game_images. Then, since a game can be on many platforms and platforms can have many games, I had to create a relationship between the game itself and whatever platforms it was on.

For example, if Hollow Knight is available on Nintendo Switch, PC, Mac, and Linux there needs to exist a game_platforms join of game_id and platform_id.

In order to add games to a user I couldn’t just do user.games << game because that wouldn’t tell me which platforms the user owns the game on, and it could be more than one. For example, if I own Hollow Knight on Nintendo Switch and on PC (Windows), I need a relationship between myself, the user, and game_platforms. In order to add Hollow Knight to my games, I had to find the game_platform relationship(s) I wanted:

game = Game.find_by(title: “Hollow Knight”)
platform = Platform.where("name LIKE ?", "%#{"switch"}%").first

# or if you already know the ids:
# game = Game.find(4)
# platform = Platform.find(130)

gp_association = GamePlatform.where("game_id = ? AND platform_id = ?", game.id, platform.id)

#Find the user
user = User.find_by(username: "robotspacefish")

#Add the game/platform relationship to the user
user.game_platforms << gp_association

# Repeat this for the next game_platform relationship.
Enter fullscreen mode Exit fullscreen mode

gt-users-game-platforms.png

The relationships used were actually really confusing for me (a big thank you to my cohort lead Chris for helping me set them up) and it took me a while of trying a lot of different things before I got a grasp on it.

The search feature always reaches out to the API before checking the internal database. I made this decision because new games are constantly coming out within the same franchises. So if someone searches for “Resident Evil” but the last search for it was a while ago, there might be a new title coming in the franchise that isn’t stored in the database yet. As mentioned earlier, after game data from the API is parsed and converted to an object that fits my needs, I check the database to see if the game already exists and add it if it doesn’t. After that, I search the internal database so I can retrieve all the results that match the query, including user’s custom games.

Game.where("title LIKE ?", "%#{game_title}%")

Styles

I wanted to do the styles from scratch, but I didn't want to spend too long on them, and I also didn't want to use a heavy framework. I ended up choosing something fairly lightweight called Spectre.css. I found the docs a bit lacking and ended up manually tweaking a lot and spending way more time than I initially planned anyway, but I think it looks pretty good and I'm happy with it.

Final Thoughts

  • I'd like to do some research on optimization because I feel like there might be excessive querying going on.

  • When there are a lot of games to show up on the /games page it was really slow. I did some Googling and found the culprit to be Shotgun. Apparently it "forks and reloads the application for each request." (stackoverflow). I knew we had Thin as part of our gemlist but I didn't really know what it was before seeing that answer. I started using that instead of Shotgun and the speed difference was night and day. I imagine the reloading Shotgun does is important for development mode, so I'll continue to use that and switch to Thin here and there when I want to see how everything is working together.

  • One of the hardest things (besides the confusing Activerecord relationships) was remembering to git commit after every little change. It's really hard to stop coding when you're on a roll.

Top comments (0)