How I Built a Fitness Tracker App w/ Sinatra
Building a Sinatra application was, by far, one of the most challenging projects I've undergone so far. I'm not gonna sugarcoat it: this was a difficult project. Or, at least, I made it unnecessarily hard in the beginning. But after much struggle and persistence, I've finally came up with a minimum viable product that I'm proud to talk to you about! This project tested my knowledge of a variety of skills in Ruby and Sinatra, including:
ActiveRecord/ Object-relational mapping
CRUD (Create - Read - Update - Delete)
RESTful Routes and HTTP
Sessions & Cookies
User Authentication/ Password Encryption (using the
As you may well know, Sinatra is a domain-specific language that is basically a simple, lightweight version of Ruby on Rails. It's an excellent web framework for getting started with building Ruby-flavored websites. For database management, I'm using ActiveRecord to map my model data to the database and handle migrations. Because my site will involve a user creating an account and housing their own CRUD-able content, we need a database that allows the site to fetch and render the correct data for that user.
For my project, I created a simple fitness tracker that let's users design workout routines.
Phase One: Research & Planning
(Author's note: I can't stress enough how important this first phase is. A big reason why I was struggling with implementing my research in the actual code was because I wan't planning well enough. Planning is an invaluable skill; once I went back to the drawing board and regrouped, going forward felt a lot smoother)
"Give me six hours to chop down a tree and I will spend the first four sharpening the axe." - Abraham Lincoln
Since this fitness tracker was going to be a Sinatra application, I knew I had to spend time thinking about what a typical user experience would be like. In other words, I wanted to map out what routes the user would (or could) take and how that experience would unfold until they logged off.
In order to better visualize this login-to-logout user experience, I decided to make a simple flowchart on diagrams.net.
In a nutshell, all this flowchart is showing is a general mapping of what a typical user experience (or session) would be like on the site. From the start of the server, a user can log in or sign up for an account. Then, a user can either view the routines they'd made thus far or create a new one.
Within the form used for creating a routine, a user should be able to add 1 or more exercises to that routine (
Routine has_many :exercises and
Exercise belongs_to :routines). Upon creation, the user is redirected back to their list of created routines. From there, they can view a specific routine and/ or they can either edit or delete it. The user should also have the option of editing/ deleting one of the routine's exercises. Essentially, much of the CRUD work for exercises are taken care of in the
When a user logs off from their account, the session should be cleared of any and all data that pertains to that user. This is to prevent another user from stumbling into your account and having access to your data.
Phase Two: Implement
Implementation is what I consider to be the longest phase in the process. This is because, in my experience with this project, there were times when I wanted to implement a cool feature without having laid the basic route-work to make the site functional. I wanted to run before I could walk.
Because my project had to incorporate the Model-View-Controller pattern and have a database to keep track of the model part, I went with Active Record. Active Record handles all the toil of mapping my models to a database (creating tables and columns, conducting database queries, etc.). Awesome!
Thanks to the handy
corneal gem, setting up the folder structure for the project was as simple as
corneal new flatiron_gym_app. It also provides me with a Rakefile, giving me access to a litany of tasks such as database management and console debugging. So not only do I have a solid project structure, I've also saved myself a lot code to write!
Phase Three: Debug & Improve
During the course of this project, especially during the really difficult times, I had been going back and forth between Phase Three and Phase Two. Debugging and improving usually leads to new ideas and new approaches to the problem(s), which usually leads to me asking "how do I implement this?" (Phase Two).
The hardest part about building this project was improving the flow of the action controllers. Action controllers handle the "C" in MVC by processing request data (primarily POST data) from the routes and transferring them to the views. One of the reasons I had such a hard time learning this was because at first, I wasn't clear about RESTful routing and how it works.
Eventually, I was able to create all seven of my RESTful routes, "wire" them between my models and my views and start creating, reading, updating and deleting stuff!
Don't Overthink & Always Keep the MVP in Mind
When I was first trying to get the routes to render what I wanted from the
current_user (things like their name, their routines, their exercises) I ran into a big issue. And it was over two small, but critical, bugs.
When a user would log in to their account to view their routines, the
session hash was somehow not carrying the
user_id property that I needed to access, and render, the
current_user's information. In other words,
session[:user_id] was returning
nil by the time the user got to their Routines page.
The issue? I was inadvertently redefining the
session hash with this line of code:
post '/login' do user = User.find_by(email: params[:email]) if user && user.authenticate(params[:password]) session[:user_id] = user.id @session = session redirect '/users' else redirect '/login' end end
This causes the
session hash to redefined as I was being redirected to another page in the site. In other words, by the time I was logged in, the session forgot who I was! Eventually, I found the bug in
@session = session and removed it.
I actually moved this code to a session controller and added some more stuff to it but here is what the revised code looks like:
post '/sessions' do @user = User.find_by(email: params[:email]) session[:user_id] = @user.id redirect '/users/home' end
Be Patient with Yourself
There came in the point during this whole experience where I wanted to give up and start a completely new project with a different code base and different models, associations, etc. I had almost convinced myself that this fitness tracker app was too forgone and could not be saved.
But then, with some guidance from my mentor, I realized that, at the end of the day, this is the only thing that matters: be patient with yourself. I needed to learn that this stuff is hard and relatively new to me as a developer. I have to forgive myself for not getting something right the first time and learn to relax and believe that I can master it.
However, this idea that I just had to "believe" that I could master the topics covered in this project wasn't enough. Realizing that this project was hard and that it required solid research and effort was only half the battle. Actually doing it, and realizing when I fell short, was the real challenge. It wasn't enough to just think I had a "growth mindset". According to Carol Dweck, who's incredible lead to the popularization of the term, we all have "fixed-mindset triggers" (the opposite of a "growth mindset") that are caused by periods of high adversity. This enflames the insecurities we may already be feeling when delving into a new project. (Dweck, 2016)
The most important lesson I believe I took away from this project was that asking questions and admitting you don't know something isn't a weakness. It's a strength. My partner reminded me that because I'm in a coding bootcamp, I'm in a school. Therefore, I have "professors" and "TAs" that are there to help me when I'm stuck. It took a lot for me to work up the courage to go on Slack and DM my cohort lead.
But you know what? Imagine how much time I would have saved if 15 minutes passed by and I was like "I don't know this/ don't know what I'm doing! Time to reach out the community for help and guidance." As opposed to waiting until the day before the project is due...
Case and point: Ask questions and don't be afraid of admitting you don't know something.
I know this project could have been a lot better, but I'm really glad that I stuck with my original and idea and saw it through to the end. I can only get better from here on out as long as I'm honest with myself and correct course.
P.S. Feel free to check out my project by heading over to the repo on GitHub!