Sinatra and the Racqueteer: crafting my first model-view-controller web app

Monday 1 February 2021 / coding


The second major independent project of the Flatiron software engineering bootcamp requires that you build a Sinatra-based model-view-controller web app with ActiveRecord integration. Initial ideas included:

In the end, however, I decided to build an app that would allow you to keep track of your racquet sports activities by:

Models, migrations and modules

The only problem with this domain idea was that it's one I'm pretty familiar with, which led to building a fairly large and complex object-relational map - relative to what I've worked on so far - as can be seen in the entity relationship diagram below. Two PDF versions are available for a clearer view: landscape | portrait.

Entity relationship diagram

Almost all 15 models (or objects, or entities) have different relationships, which required some careful modelling of relationships and construction of migrations. Relationships in Racqueteer include:

I created a couple of modules to support the models that contain name attributes. The first added functionality covered in Flatiron labs: generating slugs by name and finding instances by slug. The second module required, included and extended the first and established a belongs to relationship with users, avoiding repeating these elements of code in models with shared behaviour/relationships. The latter took a while to figure out, but I eventually discovered the concept of 'base' in modules, and used the method self.included(base) and within that methods base.send, base.extend, base.include and base.belongs_to to do the rest.

Controllers

Sitting between the complex models and complex views (more on that later), the controllers had to do some heavy lifting. The varying relationships between different models had implications on controller logic, one prime example being methods that invoked SQL queries in order to feed lists of objects, either on index or show pages or in providing options for inputs based on existing data. I used SQL methods - mostly in helper blocks - in order to sort and filter the data in a sensible way, which would've been harder to achieve using more standard Ruby and ActiveRecord methods. A number of these methods could feed data to views from multiple controllers, however some controllers needed unique sorting or filtering and therefore custom code. Figuring out the similarities and differences between the needs of different controllers and views was an ongoing process throughout development, requiring refactoring of code both to select additional columns in SQL queries and to move the methods between controllers.

In addition to helper methods, I also made use of before hooks to preprocess requests, including:

Another area of complexity within the controllers was destroying associated data. The app allows users to directly request instances of each model except the low-level join tables and results to be destroyed. However, since I chose to require almost all fields for every model, this meant destroying associated data. In the case of racquets, it's not enough to simply destroy a racquet's matches and match racquets, since that would leave behind any match racquets belonging to other racquets but the same match. In order to account for these, I iterated over a racquet's matches (using the ActiveRecord methods added by the has many through relationship), destroying all of each match's match racquets and then the match, before destroying the racquet. Effectively I went through the join table, then looked back at it from the other side. The same applied to coaches and coaching sessions.

Views

With the views, I tried to step things up from what I'd done before, integrating embedded Ruby (ERB) logic, dynamic forms, CSS and JavaScript to create a sense of polish in the user interface (and user experience). Since this post is already pretty long and there are lots of things I could cover here, I'll stick to listing some of the key technologies and features.

HTML and ERB
CSS
JavaScript

Deployment

Lastly, I decided to try deploying the app via Heroku. Beyond trying to find clear documentation on how to do this (and particularly how to do it with Sinatra versus Rails), I encountered a couple of hurdles in achieving this.

First, SQLite is not accepted. SQLite contrasts with many alternative SQL implementations in that it stores the database within the app's own directory, rather than on a dedicated database server. Heroku does not accept this in production environments - i.e. for publishing on their platform - and recommends PostgreSQL. I had no idea how to do this, and found the documentation equally mystifying at first. Eventually I figured out how to get this set up locally through my Windows Subsystem for Linux setup and could test the app. Interestingly, when serving the app locally while using PostgreSQL, some SQL query typos broke the app in a way that was not the case with SQLite.

Once PostgreSQL was sorted, I tried deploying the app again, and it made it through the deployment process! However, when I opened the app on Heroku, it returned a generic error code meaning something is wrong. Something. Yay! After a bunch more research, I realised I was missing the essential 'Procfile', which tells Heroku what command to run to start up your app. The only problem was I didn't know what to put in that file... Eventually I thought of looking at the GitHub repository of another Flatiron Sinatra project I'd seen deployed on Heroku (Aurangzaib Danial's Catchup! RSS reader), and copying that was a success!


Below is a demo of the app, which is hosted on Heroku and can be accessed via racqueteer.yndajas.co (you will be redirected to Heroku).