Project 2: COVID Stories — A Sinatra web app

Screen grab walk through of COVID-stories web app
 

Update:

My app is live via Heroku. Click here to navigate to it.

First Obstacles

Here is a link to my github repo for this project.

This app was built with the intent of making a sort of “post secret” space for people's experiences in the COVID-19 world. The spirit of the app is to have an anonymous space where you can share stories without judgement or fear of retaliation. This is my second Flatiron project — the goal being understanding HTTP routes, user validations, ActiveRecord, SQL and database management within a Sinatra framework. While it's meant to be an anonymous platform, you can create an account and manage your content, mostly out of an obligation to the project requirements.

This was our first foray into actual web development. Initially I struggled a lot with HTTP routes, or at least I thought. On one of the earlier route labs, It just did not make sense, and it was really frustrating. Later I realized that I wasn’t refreshing the right page in order to see the changes I made — so dumb in hindsight.

I often run into this as a newbie: If something isn’t working right, I jump to the assumption that it’s my ineptitude or inability to grasp the concept. It couldn’t possibly be something as simple as not refreshing. So you end up spending time debugging something that isn’t there because of self-doubt. Always a work in progress.


Gaining some confidence

Once I began to trust my understanding of HTTP routes, things went more smoothly. It really was a long journey of following the HTTP thread. I found, as well as my fellow cohort compatriots, that the best way to debug routes is to put up a big giant billboard on the other side of your route. Wherever your next GET request is going to, put a giant <h1>title that says “You made it to the (page you’re looking for) page! Congratulations!” From there it’s easy enough to gather the appropriate data in your controller and display it on that page.

It was also really helpful to follow the RESTful route architecture. Standardized systems make it really easy to build your navigation concisely, and it also makes it easy for other developers to work with your code.


Building the Data

My application has a very simple database structure. A user has many stories, and a story belongs to one user. Thanks to ActiveRecord I can create those SQL association by literally saying the User model “has_many” stories, and Story “belongs_to” User.

“class&nbsp;Story&nbsp;&lt;&nbsp;ActiveRecord::Base&nbsp;&nbsp;&nbsp;&nbsp;belongs_to&nbsp;:userend”
 

My User class looks a little different, because that is where I set up password encryption and username validation, which I will get into later.

class&nbsp;User&nbsp;&lt;&nbsp;ActiveRecord::Base&nbsp;&nbsp;&nbsp;&nbsp;has_many&nbsp;:stories&nbsp;&nbsp;&nbsp;&nbsp;has_secure_password&nbsp;&nbsp;&nbsp;&nbsp;validates&nbsp;:username,&nbsp;presence:&nbsp;true&nbsp;&nbsp;&nbsp;&nbsp;validates&nbs…
 

ActiveRecord is so amazing. Just by declaring the “belongs_to” and “has_many” relationship in my models (and having them inherit from ActiveRecord::Base) I can easily create actual Class objects the relate to each other appropriately. After building dynamic ORM apps from scratch in earlier labs, I greatly appreciate what ActiveRecord is doing for me here. It’s a lot.

I should back-track. Before setting up my ActiveRecord models, I created my database migrations in order to create my data table.

class CreateUsersTable &lt; ActiveRecord::Migrationdef changecreate_table :users do |t|t.string :usernamet.string :first_namet.string :password_digestendendend
 
class&nbsp;CreateStoriesTable&nbsp;&lt;&nbsp;ActiveRecord::Migrationdef&nbsp;change&nbsp;&nbsp;&nbsp;&nbsp;create_table&nbsp;:stories&nbsp;do&nbsp;|t|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;t.string&nbsp;:title&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;t.strin…
 

The :password_digest line of the Users table is an important part of how ActiveRecord’s password encryption works. By naming your column “password_digest,” ActiveRecord created the attribute “password” for your User class, but stores it under the column “password_digest” in an encrypted format using the “bcrypt” gem. This means that even I, the developer, don’t have access to the passwords in the database.

Building it MVC

After building the database and models (using ActiveRecord), the remaining parts of the “MVC” application (Models, Views, Controllers) were to create views and controllers. I found the easy way to do this was to navigate the app from a user-interface point of view. So if a user starts at the home page, let’s build the “/” route and controller, and if the next place they go is the index page, let’s build the “/index” route and controllers, and so on.

One of the requirements for this project was to have CRUD (Create, Read, Update, Delete) functionality. So, I made sure my users could do just that with their COVID stories. That work happened almost entirely in the “StoriesController".”

class&nbsp;StoriesController&nbsp;&lt;&nbsp;ApplicationController &nbsp;&nbsp;&nbsp;&nbsp;get&nbsp;'/stories'&nbsp;do&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;current_user&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs…
 

There are some helper methods not shown above, but you get the idea of the narrative that’s taking place in each of these routes. For example, in get ‘/stories/:id/edit” I’m basically saying, get the current story (having just come from a story “show” page) and its data. If you’re logged in, and your session id matches the id of that story, then show the “edit” page where you can edit that story. This is a form of validation where if you navigate to the url of the edit page of this story in your browser, my app won’t let you edit the story you’re looking at unless you’re both logged in, and that your logged in credentials match those of the story creator’s. Otherwise, you get redirected to your personal story index page so that you can edit your own stories (if you’re logged in and the story’s not yours), or it will still bring you to the edit page anyways (having not met either of the first conditions), except on the other side of it is some conditional formatting waiting for you that will tell you you can’t edit that story.

You must be&nbsp;logged in&nbsp;to edit stories.
 

Conditional Formatting

I did a lot of conditional formatting on the “welcome” page, which was a fun process.

&nbsp;&nbsp;get&nbsp;"/"&nbsp;do@stories&nbsp;=&nbsp;Story.all@sorted_stories&nbsp;=&nbsp;@stories.order(id:&nbsp;:desc)@random_stories&nbsp;=&nbsp;@stories[0..-2].sample(10)&nbsp;&nbsp;&nbsp;&nbsp;erb&nbsp;:welcomeend
 

This is the get route for my welcome page. It’s storing three things in instance variables to be made available to the “welcome” view. @stories is the entire collection of stories. @sorted_stories is the stories sorted newest to oldest. @random_stories is the one I wanted to talk about the most though.

It will make more sense after seeing my welcome.erb file.

<h2>Covid Stories</h2>
<strong><%=@stories.last.title%></strong><br>
  <%=@stories.last.content%><br><br>
  <%if @stories.length <= 10%>
  <%@sorted_stories[1..-1].each do |story| %>
    <strong><%=story.title%></b></strong><br>
    <%=story.content%><br><br>
    <%end%>
  <%else%>
  <%@random_stories.each do |story| %>
    <strong><%=story.title%></b></strong>
    <%=story.content%><br><br>
    <%end%>
  <%end%>

Here I’m displaying the title and content of the most recent story. Then I’m iterating through all of the available stories except for the newest one (“@sorted_stories[1..-1]”). But before that I’ve created an “if” statement of “if @stories.length <= 10.”

Conceptually what I’m saying is “If there are ten or fewer stories, display them all, newest to oldest. Otherwise pick ten random stories from the collection and display them, but still display the newest story created at the top.” The idea is that if the database ended up being populated with hundreds or thousands of entries, you would see new content every time you visited the welcome page because it would be a random sample of stories. So you could refresh the page and see something new every time.

The line “@random_stories = @stories[0..-2].sample(10)” from the ApplicationController is how I was able to do that. “.sample” choosed at x number of random items from a collection, “x” being specified in an argument. In my case, “10.”

Other Validations

In addition to ActiveRecord back-end validations, I also implemented some front-end validations using simple HTML. For example, for my username input on my signup form, I put:

 <p><input type="text" placeholder="Username: " name="username" pattern=".{3,20}" required></p>

The phrase “pattern=".{3,20}" required” makes the input field a required field, and it also makes it so the input must be at least 3 characters with a max of 20.

Some other ActiveRecord validations I used are “validates :username, presence: true” and “validates :username, uniqueness: true.” The presence validation just makes sure the username isn’t blank, while the uniqueness validation checks whether or not the username already exists in the database, so you can either log them in if they already have an account, or deny their signup form if that username already exists. From there, in my post ‘/users’ route I can check for errors using ActiveRecord’s “.valid?” or “.errors.” You can also use “.errors.full_messages” to print some nicely formatted strings of your validation errors. That looks like this:

Error:Password confirmation doesn't match PasswordUsername has already been takenPlease return to signup and try again.
 

ActiveRecord validation will also check if you have two identical fields in a row (like a password confirmation) and check that they match. That’s a validation built into ActiveRecord that you don’t have to explicitly setup other than having two password fields in your form.

Anonymous Posting

The anonymous posting functionality (vs. the logged-in posting) operates pretty simply: If you’re not logged in and you create a post, that post has no user_id. If you are logged in, it does. That way the main show page (welcome page or get ‘/’) shows any kind of story whether or not it belongs to a user, but if a user wants to view their stories or edit them, they only have access to stories that they have created.

Takeaways and Next Steps

To simplify my life, I stripped away all of the CSS from my project, but the next thing I want to do is revisit the HTML and CSS curriculum and really get a better handle on those concepts. I have experience doing hacky things with CSS and HTML, but it can’t hurt to have more practice. My next project won’t be so bare visually, although I do think it works well aesthetically for this particular project. I really wanted it to be just about people’s stories.

For a video walkthrough of my app, please see below: