Third Project: Rails
So back again, we’re in the last day of phase 3, and I’m wrapping up work on the third project in Ruby on Rails (totally not procrastinating on writing the blog of course). Sticking with my theme, I’ve made something of a fantasy hockey application. The application pulls game data from the NHL’s stats API and stores teams, players, games, goals and assists. Then users are able to create accounts (in a normal “sign up” fashion, or via Google’s OAuth sign-in), make “matchups”, where they can pick players that have played in games related to the matchup, and compete based on whose players accumulated the most points in those games.
Before I go any further, I just want to say that the idea for this application is entirely my friend’s, Thomas. Quite a while ago, assuredly while talking about project ideas outside of a hockey rink somewhere in Pittsburgh, he brought up an application based on a game he used to play with co-workers, where they’d pick players in a playoff series and track whose players had accumulated the most points. We discussed that this was something we’d work on in the future, but it hadn’t really materialized up until now. Going through this course, this idea has been in the back of my head the whole way, and going into the Rails project, I felt that I had all the tools necessary to make it happen.
Ok so lets get into it. I started out the project by trying to plan out models. I knew roughly what information I’d need to pull from the NHL’s API (teams, players, games, goals, assists), and also roughly what I’d need to track on the user’s side to make matchups (users, matchups, picks). Here’s a picture of what I’ve come up with (I apologize for how much this looks like a board tracking a conspiracy theory):
One of the big problems that I ran into while planning out models was that there are several things about the player model that are not necessarily static. A player could be traded and play for a different team, their jersey number can change and, albeit less likely, their position can change. My solution to this was moving this information to the GamePlayer model. A Player is just a name and API id, while a GamePlayer tracks the player, team played for, game played in, jersey number and position. I’m not really sure if a triple join table is a thing, but it seemingly worked here. That said, I’d be very interested in looking further into if this is the “right way” to deal with this problem.
I’m going to skip past explaining most of the rest of the models, as I feel it’s mostly self-explanatory; however, I do want to point out that in some cases where it might look like there’s a many-to-many relationship, I’ve not used the ActiveRecord
has_many :model, through: :other_model macro, as I don’t want all of the records on the other side of the relationship. An example of this is the Picks model. A Pick
belongs_to :player, but Pick does not
have_many :goals, through: :player — there’s a custom method in the Pick model that retrieves only the goals of the player related to the pick’s matchup:
So with the models more or less set in stone(they definitely changed around a bunch throughout), I set up my sign-up, login/logout functionality(standard, not through OAuth) and moved on to working on the fetcher. While working on the CLI project, I was able to just create a Fetcher class, and that was sufficient; however, trying to adapt this to the MVC / Rails separation of concerns, I was having trouble deciding where a fetcher would fit into the equation. Certainly a fetcher isn’t directly related to MVC, and although the methods I’m looking to write will be employed as a “job” at some point, the job should be able to call the fetcher methods instead of having the methods written in the job. After some research, I discovered services. Although a services folder isn’t built automatically when rails makes a new project, you can make one in the App folder, and it’ll be loaded as normal when your application is started.
With this idea in hand, I created the services folder and FetcherServices module inside
fetcher_services.rb. The general idea is that when starting with a clean database, the fetcher will pull all of the teams from the API. Then, with user input, the fetcher will pull all of the games scheduled for a certain period of time (currently I’m using a season, but this could be easily modified in the future). Once all of the games are created, the fetcher goes through each created game, fetches their API details page, find_or_creates all of the players on both teams’ rosters and, finally, creates the goals and assists from the game. All of these methods are employing some sort of find_or_create, so if you run the fetcher pointed at the same teams/season/games, you shouldn’t get duplicate data.
With my test data ready (all of the games from the 2018–2019 season), I moved on to working on the actual application. For the most part, working on this side of the application was pretty standard. A user can create a matchup and invite people to join that matchup. Once there are between two and four users, the owner of the matchup can begin the draft phase. From the matchup show page, users can go to the draft table page, where they can pick a player if it’s their turn(draft order is randomized when the draft is started). Once all users have four picks, the matchup moves to an “active” status, and will remain in this status until all of the related games are in a “final” state. Once all games are final, the matchup will move to a “completed” status, and display a winner on the show page.
Throughout this process, the matchup show page will show information related to the matchup in it’s current state. In all states, it will show general matchup details (name, start / end date, team, owner, status and related games). In the pre-draft state, it will show users and invitations. Draft will show all users with related picks(so far), and stats from the related season. Active and Complete will show all users with picks and stats from the games in the matchup. As above, Complete will declare a winner.
Last, and I just got around to this yesterday, I created a dashboard! I wasn’t entirely sure of what I wanted to display here; however, I’ve currently settled on four tables that will display matchups with action required(it’s your pick in the draft phase), active matchups (up to 5 matchups that have an active status), recent picks(your last 5 picks), and your best picks (5 picks with the most points from all matchups… probably not a very efficient query to have on the dashboard). Needless to say, I had some fun trying to figure out how to write these.
Until next time.