Originally posted on March 20, 202` as a project requirement for Flatiron School
I have been a crafter for a couple of decades now. If you've met me over zoom, you will see a whole wall of yarn and related implements behind me in my home office. I can spin my own yarn from wool or alpaca fibers, I can knit, and I can crochet. The downside to having this particular hobby is the lack of digital support. There are little apps you can download to keep track of what pattern row you're on, but there aren't many for quick references when you're standing in the yarn aisle wondering if you already have a particular yarn in your stash at home.
I made yarn stasher because it's something I would use, and it's something my friends could use. The basic idea is that a user can sign up, log in, and create yarn objects that will be stored in a table view for them to reference. I wanted it to be quick and easy to use. Users cannot view other users' stashes, but they can view individual yarns if another user sends them a link to that yarn's show page.
I started my project by using the Corneal gem to build out my boilerplate code and filesystem. I added a couple of folders for views so that they could be separated by model. I added controller files (users_controller and yarns_controller) which inherit from the application_controller file. Next, I removed some gems from the gemfile that I don't need (like database cleaner) and ran bundle install. Now I'm ready to get coding.
The next step was to create the models. This app currently has two models: User and Yarn. A User has_many yarns and a Yarn belongs_to a user. I added some ActiveRecord validations to my User controller to ensure that each user has a unique username. Then I made use of the bcrypt gem to protect the user's password by adding the has_secure_password macro to the User model. Both models inherit from ActiveRecord::Base.
Next, I set up the UsersController and YarnsController files and mounted them in the config.ru file. While I was there, I added access to Rack middleware with the line "user Rack::MethodOverride" so that my app will have access to HTTP verbs other than GET and POST. This is the line that makes PATCH and DELETE requests possible.
Back in the ApplicationController, I enabled sessions and set a session secret. Now that everything is connected, I can save and get to work on my migrations.
I created two migrations with rake db:create_migration: one for the users table and one for the yarns table. My users table has four colums: username, email, password_digest (so that bcrypt will salt and hash the user's password), and timestamps (not necessary at this point, but could be useful later). The primary key is assigned automatically and I do not have to add a column for it. Thanks, ActiveRecord!
The yarns table has 6 columns. Name, color, weight, fiber, user_id (where the yarns table joins the users table) and timestamps (again, not needed now, but could be useful in the long run).
I then created a seed file to fill the table with data. This will be useful for anyone who is testing out the app: it gives the database some dummy data to play with and test the app's functionality.
I then ran rake db:migrate, which went smoothly, and then rake db:seed. I now had a database with a couple of users who each have 1 or 2 yarns. Time to start working on controllers and views.
The first thing I did was to set the index get route. Corneal initially sets this route to render a welcome.erb view. I deleted that view and replaced it with my own index.erb file, then populated it with a header so that I could test that my route worked correctly. I updated the get route for the index page to render my index.erb file, then moved on to the users controller.
In order to create a RESTful MVC app, I hashed out the routes I wanted to create and filled them with comments saying what I want each route to do. The UsersController should have the routes related to the user actions: logging in, signing up, creating a new user, redirecting to a landing page, and logging out. So I hashed out a get '/login' , post '/login', get '/signup', post '/signup', and get '/logout' routes. I initially also had a get '/users/:id' route that rendered a user show page, but decided later that it was redundant and created a security problem where other people could see a user's show page, so I removed it and used the /yarns/stash.erb view as the read route for both users and yarns.
With the UsersController created and hashed out, I began filling the routes with their required variables and logic. As I worked, I realized that I would need some helper methods because I was repeating some of the same lines of code over and over. So I went back to the ApplicationController and added some helper methods to set the current_user and determine if the user was logged in.
I continued on, creating views with the necessary forms as I went. The views/users folder in my app has two files: signup.erb and login.erb. The index and layout erb files are contained in the parent views folder. Both the signup and login files now contained the appropriate HTML form for both functions. I later added a "back" button to both forms so that the user could easily return to the welcome page if they chose the wrong option.
I have made the login page so that a user will only be logged in if they enter credentials that exist in the database. I also used the authenticate method from bcrypt If they enter unknown credentials or submit an empty form, they will be sent back to the login page again. I later added flash messages to tell the user why their login attempt failed.
The signup page will only create a new account for a user that does not already exist in the database. I did this by checking the entered username against the usernames in the database. If a user with that name already exists, the user is redirected back to the signup page and shown a flash message that asks them to log in if they already have an account or choose a different username if they do not have an account. If a blank form is submitted, the signup page will reload and show the user a flash message telling them they need to fill in the form completely.
Once a user is created and logged in, they will land on the yarns/stash.erb view. So this is the next view I set up. I made a simple header that greets the user, then went to the YarnsController and began hashing out the routes and logic needed. The YarnsController has a get '/yarns' route that renders the landing page which will display all of the yarns created by that user. It has a get '/yarns/new' route that renders a form for creating a new yarn, and a post 'yarns/new' that creates and persists the new Yarn object to the database, then loads the show page for that yarn, which is rendered by a get '/yarns/:id' route. This route sets the yarn so that the view has access to its attributes, then renders the yarn's individual show page. It also has a get '/yarns/:id/edit' route that renders a form to edit a given yarn, and a patch '/yarns/:id' route that updates the given yarn based on the user's input in the edit form. Finally, there is a delete '/yarns/:id' route that checks if a user owns the yarn they are trying to delete, then deletes the yarn and redirects the user back to their stash page.
When I was writing these routes, I realized I was setting yarn variables repeatedly with the line: @yarn = Yarn.find(params[:id]), so I contained this in a private helper method.
I also realized I was checking if a user owned a yarn repeatedly, so I added a helper method to the ApplicationController to help me check if a user is authorized to change or delete something.
Now that my application is behaving the way it should, I can get to some UX stuff. Mainly adding buttons, making navigation easier, and doing a little styling.
I started by adding a nav bar, which allows the user to get around the app and contains a link for logging out. This nav bar is viewable only when a user is logged in. I added some CSS styling to my anchor tags to make them appear horizontally across the top of the viewscreen, give them a background color and a hover action.
Next, I added flash messaging at the top of the wrapper div where all the content is displayed. This will make the flash message appear (if it exists) at the top of the content area where the user will see it.
I then added some styling on my navigation links, gave the app a new background color, and added a favicon that will show up on the browser tab.
Finally, I used CSS to style my forms and the table that displays all of the user's yarns.
I feel pretty confident at this point that my project meets the criteria, but I have a couple of stretch goals:
- More attributes for the Yarn objects: yardage, number of skeins owned, perhaps a field where users can record where they bought the yarn.
- Some sort of sorting option would be good. Allowing the user to sort their list of yarn objects seems like a handy UX feature, especially if the user has a long list of yarns.
- I need to comb over my code to make sure I've taken care of redundancies or see if there is a better way to refactor some of the logic in my controllers.
I'm going to take a few days to see what else I can make this app do, and then I will be satisfied to add it to my portfolio.