Building a Wine Journal Application with Sinatra

Building a Wine Journal Application with Sinatra

I feel good going into project mode for the Sinatra application.

With my Burger Finder app under my belt, I know what worked, what to expect and what did not work for me last time. I wanted to approach this project with a plan so that I could work efficiently.

I keep a running list of ideas. One that I had been wanting to build for years was a digital wine journal. I have a bad habit of not writing down or taking pictures of wines that I tried and liked. The Sinatra app requires a MVC model, with CRUD commands, so it sounded like a good fit.

Planning

I went into detail on planning with my CLI application and followed the same principals in building Wine Journal. Project week was actually two weeks, so I took the first to plan and the second to code. During the first week, I finished any existing labs and revisited new concepts until I was comfortable with them.

I wrote user stories to help me think about the user and how I wanted them to navigate the app. These two were the most important to me:

As a user, I want to see a list of wine bottles that I have tried and added to my collection

As a project maintainer, I want user information to be secure

Writing user stories helped me to think about which RESTful routes I would need, how to structure the /views folder and what methods I would need to achieve what I envisioned for the app.

Sketch of the proposed flow, view and routes for a logged it or logged out user
Sketch of the proposed flow, view and routes for a user.

For Wine Journal, I wrote out the bulk of my application in pseudocode. Pseudocode for Pros explains that by putting time upfront to pseudocode, it saves time later when it comes to writing the code.

# Pseudocode for creating a new bottle object

  • if not logged in
    • redirect to /index
  • else (if they are logged in)
    • require name only
    • check for blank fields
    • create new wine bottle object
    • save to the database
    • associate user with the wine bottle they created
    • wine bottle shows in their collection
    • redirect to /bottles/id
  • end

Looking back, although my pseudocode did not account for everything, it covered at least 80% of what I needed to build.

Building the MVP

The main goals of the Wine Journal were the following:

  1. Have at least 2 models, 1 has_many and 1 belongs_to relationship
  2. Have user accounts where a user can perform CRUD operations only on objects that belongs_to them
  3. Use RESTful routing
  4. Enable user sessions

I met the first requirement by having a User and a Bottle model. A User could have many (wine) bottles in their collection. And a Bottle, a specific entry of a wine bottle, belongs to a User.

For the second requirement, I set up a /signup route. The view included a form with inputs for a name, email address and password. On submission, if none of the inputs are blank, a new User is created, they are logged in and directed to the welcome page.

Wine Journal Signup Form
Signup form for the wine journal app
Signup method checks for duplication accounts and empty input fields

Once logged in, a user would only be able to view, edit or delete bottles in their collection. I handled this in two ways. First, when navigating, a method would check to see if someone was logged in. If they were, then it would check to see if they had permission to view the page.

Get /bottles route checked to see if there is a logged in user. If they are logged in, bottles from their collection are show in the erb view.

Second, as a fail-safe, when a bottle was being edited or deleted, the method would double check that the id of the current user matched the foreign key (user_id column) of the bottle. If everything was truthy, the patch or delete request would go through. If there was a failure, they are redirected to the /login page.

Patch method checked that the current user has permission to edit the current bottle

Enabling sessions allowed me to check if a user was logged in by assigning their user id to session[:user_id]. When navigating to the /logout route, the session has is cleared and the user is directed to the / route.

Using RESTful routes, get  post requests and forms was difficult. I kept getting the routes wrong—did I need patch or post if I was using form inputs to create an object. But, I took it slow and got comfortable with the concepts. I started with basic routes, get routes like  /signup and /bottles before creating the patch and delete routes. I almost forgot to include use Rack::MethodOverride in config.ru which allows the Sinatra Middleware to send patch request. Oddly, post was working in place of patch but there may have been long-term effects.

I avoided working on sessions. Even though this app did not have real user data or API keys, I wanted to keep it as secure as possible. I had the idea to use environmental variables to reference the session secret word, but did not know how to do it. I saw some references to hiding API keys with Javascript or Rails, but not within the context of my Sinatra app.

I learned about the dotenv gem, worked without Rails and seemed to be the solution I was looking for. After installing the gem, creating a .env file and adding it to a .gitignore secret keys could be added to .env and referenced in the application.

For example, instead of set :session_secret, "secret" in the application_controller, it could be set :session_secret, ENV["SESSION_SECRET"] which references the SESSION_SECRET> within the .env file, which contains like this SESSION_SECRET="secret".

Plans for the future

I feel like this project went really well. I gave myself the time and resources I needed to succeed. However, there are improvements I would like to make. For a future update, I want to refine this app so that I can deploy it to a service like Heroku or Netlify so that I can access it on the web. Learning to hide sensitive information was a step in the right direction, but there may be additional layers of security to implement. I will have to think about storage, deploying changes, testing and monitoring the app to ensure any vulnerabilities are taken care of.

Keeping Wine Journal DRY was a large part of the project requirements. Overall, my application was DRY, but I know there is room for improvement. I like to take some time away from a project (and after it has been reviewed) before I refactor it. That way, there is less risk of breaking it ahead of it being reviewed.

I would like to refactor common or similar patterns into new helper methods. At several points in my application_controller I confirm if the current user has permission to modify an object. This can be refactored into a has_permissions? method and referenced within other methods.

The routes and redirects I set up keep the user from getting stuck at a page with no direction. However, error messages to explain why they have been redirected is something that I want to incorporate. The Sinatra Flash gem is one I am looking into.

The scope of this project did not include accessibility, load times or progressive web applications (PWAs), but I would like to incorporate those concepts. I learned about PWAs last year during the Udacity, Mobile Web Specialist Nanodegree and would love to do more! It would be really cool to be able to access the app offline, store any changes locally then push them to a server when the connection is available again. I’ll be able to add wine notes anywhere!

Overall, I am happy with this project and look forward to being able to add to it.

The code for Wine Journal is available for use on Github.

Screeshot of Wine Journal homepage
Screenshot of Wine Journal homepage

Wine bottle icon provided by Font Awesome. No changes were made.


Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.