A Guitar Collector App From Scratch
Tags: ruby, sinatra
Published: 2021-04-01 21:53:30

    


For Flatiron School's Mod 2 final project, the challenge was to create a full site from the ground up using the Sinatra framework- complete with user log-in/log-out functionality, the ability to post and edit their posts, and enough security to protect each user's posts. My two major hobbies are hockey and music, and I had already done a hockey related app for phase 1's CLI app, so my brainstorming instantly went to music. I wanted to make a site dealing with guitars specifically, since the ability to create, save, and edit seemed to lend itself to a guitar collection (I have a modest collection of guitars myself. I've always seemed to just luck into them!)
    To sink my teeth into the app's creation, I installed and used the Corneal gem in my terminal- this was invaluable to setting up the various directories needed to handle the files from my database to my user's views. I had heard mixed things about this gem, and it gave me pause to hear about some additional troubleshooting needed for version compatibility with some other gems, but overall I found it to be excellent- I had to manually adjust the version of ActiveRecord but once I did that, I had my site template up and running. It gives a very basic layout but it was simple to customize the existing code that Corneal loads up instead of starting it from scratch.
    My philosophy going into this was to opt for simplicity- using very neat code and keeping my object relationships minimal. My models inherited the 'has-many' (in this case, my user) and 'belongs to' (their guitars) so all I had to do was set those macros in each respective model and I knew that a user ID would associate to each guitar when persisted to my database. I elected not to seed dummy data to my database table or pull data from an API. Instead I went with the idea that each user can fill out a form to create and save their own guitars. Each form would instantiate a new guitar, and persist it to the database by matching up each attribute from the form to each column of my table. It was important to me to be able to display a photo of each guitar, and this posed my first significant challenge. To enact this, I gave the site user a form item to supply an image url in the creation form. In the pertinent views routes, I was able to use an HTML anchor tag and dynamically pass in that url to display each guitar photo.```<img src=<%= @guitar.image_url %> alt="Guitar Image">``` In the event no url gets passed in, I was able to set a default image url to a variable, and then set a conditional that if no custom guitar picture is passed in, just display the default. I then had to pop into my CSS stylesheet and set image size parameters, to avoid having enormous pictures of guitars displaying on the page. Also- I wanted SOME fields to be required, and some (like the image url of course) to be optional- in my guitar model I had to set validations for each attribute that I wanted to be mandatory. Phew! That was a lot of steps and trial and error to get this basic functionality!
```photo_url = !params[:image_url].empty? ? params[:image_url] : "https://i.pinimg.com/originals/0b/0d/bc/0b0dbce265be5db9bf7061b4e8dc83f4.png"```
   I had a basic sketch of what routes I wanted, but I found it to be a challenge enacting those RESTful conventions at first. How do I get to my /index page? That would start with no data, so what would I display to a new user? How do I click one specific guitar from their index, and just display that? I knew I would need to take the individual ID and pass it into the route, but how do is that possible for every single guitar? After some missteps and improper routing, I followed the correct convention to show the index after login, render the form to create guitars, and a cool feature to click on the photo of each created guitar to pull up a page just showing that one's attributes. String interpolating the .id method call on that instance of a guitar in the route solved my issue of being able to single out an individual guitar to display on my '/shows' view. Then I refactored my creation form to be an edit form, and set up the proper routes, including a 'patch' request in my guitar controller file. 
   This was all well and good, and was able to display properly when I ran the Shotgun gem to spin up a local host. But what about getting users to log in and out? How do I protect each user's items they created from being manipulated by another unrelated user? At this point, anyone could type in a random ID number of a guitar in the browser and edit it all they want. This wouldn't do for an actual functional site! It was time to enable 'sessions' in my main controller, and a sessions controller (for handling each user session) and a users controller (for handling creating a new user properly).
``` post '/login' do
        redirect_if_already_logged_in
        user = User.find_by(email: params[:email])
        if user && user.authenticate(params[:password])
            session[:user_id] = user.id
            redirect '/guitars'
        else 
            redirect '/login'
        end
    end```
   Much of the curriculum in Mod 2 involved using methods that were inherited from elsewhere, which is logical in development- it only makes sense to have frameworks and gems write the common lower level methods, while as the builder of an app we are responsible for the higher level functions and specifics. This accounted for some of my learning gaps as well. Remembering and properly identifying which inherited methods came from where proved to trip me up. In order to log a user in, I had to call .authenticate on that user in a post request on my sessions controller to make sure the email passed in matched the password they set. The .authenticate method is one that I can call only because in my user model I included the has_secure_password method (which in itself is inherited from ActiveModel::SecurePassword module of ActiveRecord::Base). Once has_secure_password is loaded, this opens up the ability to a) work in conjunction with the bcrypt gem, which salts a passed in password for security (I don't want to be sued for my Guitar Wishlist passwords getting leaked!), b) writes a reader method to the model for their password, and c) .authenticate, which verifies that the password of that instance of user matches the email they signed up with. 
```class User < ActiveRecord::Base
    has_many :guitars
    has_secure_password
    validates :email, uniqueness: true
    validates :email, presence: true
end```
   This log-in feature didn't quite protect my routes- I had to create some helper methods in my main controller for that. These helper methods were little catch-all methods I would call in other methods throughout my app, just to make sure what the user was trying to do was kosher- they compared the user's ID to the session's user ID, and if the two didn't match up, it's back to the login page. Ditto if a user is logged in, we bypass the login page, or if not logged in at any point redirect them to do so, etc.
    Now I had my users, their ability to login and logout (a delete request that clears the current session), their CRUD ability to each guitar in their profile, and protection to that CRUD functionality to unauthorized users. Each step of the way to get there broke my code and threw error messages to my browser, causing plenty of frustration when I thought my logic was sound and implemented flawlessly. Through the guidance of my cohort leads and classmates, I was able to knock down each barrier and get my site fully functioning, once again reinforcing how much of software engineering is about collaborating effectively. And I have new respect for just how many factors even the simplest website has to handle!