Second Project: Sinatra
It’s the last day of Phase 2 of the software engineering course with Flatiron School, and I’m getting ready to submit my Sinatra project. Over the last several weeks, we’ve learned the basics of web development, focusing largely on CRUD operations and RESTful routes. The project requirements were to create an application that can be logged into, stores data using at least two models, and utilizes the seven RESTful routes(index, new, create, show, edit, update and delete).
Sticking with my interests, I decided to make an application that could be used to track a user’s hockey teams. You can create/login as a user, create teams, players, seasons, games, goals and penalties, as well view relative stats for those models along the way (players: points, goals, assists, +/-, PIM / team season: wins, losses, OTLs, ties). In addition to this, and this is probably my favorite feature, a user can link their account to a player on another user’s team, allowing them to view (not modify) the information relevant to that player’s team. It’s been quite a trip working on this over the last week, and I’ve learned a ton along the way.
On day one, I decided that I wanted to attempt starting the project up without Corneal (useful program for initializing a Sinatra project). There were several steps to this that I wasn’t completely comfortable with, and I did have to copy/paste a fair amount of code from one of the Canvas lessons, but I feel that it helped me better understand the setup underlying our applications. I was able to test start my application and work through the error messages until able to reach my ‘/’ route.
From here, I spent a fair amount of time getting my user migration set up, and the login/signup pages created and functional. I decided that I wanted to use Bootstrap for styling, as I’ve worked with it a bit in the past and it’s a good way to get a lot of styling done without working directly with CSS.
With my base user setup complete, I started trying to plan out my models and ran into several issues. First, the model relations would be a lot more involved than we had built in our lessons. Since there were a lot of working parts, I started out making models for users, teams, players, seasons, games, goals and penalties. I did my best to think ahead with the setup, but to be completely honest, I made a lot of changes down the road to make different fields more accessible to other classes. The other major problem was that sometimes a class would have two relationships with another class. The first and most obvious is users with teams. When a user creates a team, which they should have ownership of, this user, and no other, should be able to modify a team or anything created through the team; however, I also wanted to be able to have multiple users(teammates) able to view a team’s information. The solution to this is aliasing. This allows you to reference a related class via an alias. Having discovered this, I set up my User class to have many teams_owned, and have many teams_played_for. Teams_owned references the foreign key, owner_id, in the team class, while teams_played_for references all of the teams of all players that belong to the user:
class User < ActiveRecord::Basehas_secure_passwordhas_many :teams_owned, class_name: "Team", foreign_key: "owner_id"has_many :playershas_many :teams_played_for, through: :players, source: "team"endclass Player < ActiveRecord::Basebelongs_to :userbelongs_to :teamhas_one :owner, through: :teamendclass Team < ActiveRecord::Basebelongs_to :owner, class_name: "User"has_many :playershas_many :users, through: :players, foreign_key: "user_id"end
With this solution in mind, I was able to move forward with the rest of the models and move on to creating routes. Since most of the routes were following the seven RESTful routes, I felt pretty comfortable writing this part of the application(we were really hammering that home the week before project week). The biggest problem was making sure that my models had access to the information I wanted to provide in the views, so I had to revisit them quite a few times. Additionally, almost all of my views are not supposed to be public. All index and show routes should be accessible to the team owner and teammates, but new, create, edit, update and delete should only be accessible to the owner. I created several helper methods to handle this, since it was such a common issue: current_user, is_logged_in?, redir_login_if_not_logged, redir_dash_if_logged, owner?(thing), owner_or_teammate?(thing), and exists_and_owner?(thing).
This made writing the controllers a lot smoother, as I wasn’t repeating nearly as much of the authorization / redirect code as when starting out.
The last big step / issue was making all the stats work (and I’m not totally done with this yet). There are two statuses for games, “TBP” and “completed”. The team owner can create a game, and add goals to the game, but the game won’t be determined to be a win or loss until the game status has been changed to “completed”. Further, I modified the #save
and #update
methods to check the win/loss status every time saved. When written like this, a user can’t “fudge” game numbers (eg. give the opposing team more goals, but state your team won). With this in place, the season class is able to go through its games and set a record. #record
will return a hash containing a count of wins, losses, ties, OT losses and TBP games. #record_parsed
can also parse this hash and return it as a string in (wins-losses-ties-OTLs) format.
From there, the player class is able to pick things up to determine player stats. Since there were so many relations in the models, I was able to write methods that take in a “thing” instead of writing stat methods particular to a game, season or team. As an example, #count_goals(thing)
can take in any of those options:
def count_goals(thing)thing.goals.where(player: self).countend
With this idea in mind, I wrote several other methods in the Player class to track assists, points, +/-, PIM and games played. Currently, my views utilize these methods directly; however, I’ve also started writing a #stats
and #all_seasons_stats
method in the player class. There is a small amount of repetition, such as #count_goals
and #count_assists
being called, and then called again in #points
, that could be removed. The big thing, though, is that in the future I could package all of these stats in one hash when I decide to add things on. For now, though, I can set a variable in the views and pull the individual stats from that hash.
So, I think I’m starting to get a little too deep into the actual workings here, but there is one last thing that I was really happy with, and that’s allowing a teammate to be able to link their account to a player so that they can view, but not modify, a team’s information. In the team show page, there’s a list of players with actions. For teammates, they can just go to the player’s page, but the team owner can modify or delete the player, as well as generate a code to link a user account to a player on the team. When the teammate logs in, they can click the link “Join a Team” in the navbar, input the code and confirm they’d like to join the team, allowing them to come back and view any information belonging to the team. Probably pretty standard for a lot of apps like this, but I really enjoyed implementing it.
So that’s that. I’ve got a functional app that I’m very hopeful will pass the project review this week. Although I’m relatively certain it meets all the requirements, there’s still quite a lot of things I’d like to go back and change for the future. Outside of implementing other features, the things I want to work on are styling and refactoring. For styling, things are just kind of bland. I did use Bootstrap, but my focus was mainly on everything being functional, so there wasn’t a whole ton of effort put into styling. And the refactoring, there’s a fair amount of this project that I worked on prior to writing some of the authorization methods touched on earlier. Prior to the review, I plan to go through and make sure everything is written with the pattern I implemented in some of the later controllers (goals/penalties). Additionally, I’m concerned about efficiency and scaling. The way I access the database, I’m worried that the app will be quite slow once there’s a lot of information added or users accessing it. I’ve been thinking of ways to make this more efficient and plan to make another pass through the code when I have the time to do so.
Until next time, thanks for reading!