Building a Twitter-style web app — Chitter

The very few people who read this (Hi Mom and Dad!) may have noticed I didn’t post yesterday — I actually had a much needed mental health day. I worked on last week’s challenge from home in my PJs and watched a bit of TV (three episodes of Haters Back Off!) . Now I feel so much more rested.

I love Miranda Sings

I’m going to make this post a little different than my usual reflective ones, and write it as I do this weekend’s challenge. Hopefully this much more in depth one makes up for my lack of a post yesterday. Technically, I started on this post yesterday when I got a head start on the weekend challenge, so it counts as yesterday’s post!

Apologies in advance if the tenses change from present/future/past, because I’ll keep updating this as I go.

The specs for what we are building are are all available on the readme on my github:

To save you a click, here’s the user stories we were given to build:

As a Maker
So that I can post messages on Chitter as me
I want to sign up for Chitter

As a Maker
So that I can post messages on Chitter as me
I want to log in to Chitter

As a Maker
So that I can avoid others posting messages on Chitter as me
I want to log out of Chitter

As a Maker
So that I can let people know what I am doing
I want to post a message (peep) to chitter

As a maker
So that I can see what others are saying
I want to see all peeps in reverse chronological order

As a maker
So that I can better appreciate the context of a peep
I want to see the time at which it was made

I may end up regretting this in the end, but I’m not going to build it in the order they do the user stories. The way we did the challenge last week was to build the functionality, then add in the sign up/in/out options. That makes a bit more sense in my head. So the order I’ll do things in is:

  1. Post a peep
  2. Display peeps in reverse chronological order (reversed based on the primary key)
  3. Sign up
  4. Sign in
  5. Sign out

So this database is going to have a one to many relationship, since each user can have many tweets, but each tweet can only come from one user.

First thing to do is install all the gems I’m going to need in my Gemfile. Since I’m limited on time I’m going to install all the ones we used last week, and delete any I don’t use after the fact. They are the following:

gem “data_mapper”
gem “dm-postgres-adapter”
gem “database_cleaner”
gem “dm-transactions”
gem “sinatra”
gem “rspec-sinatra”
gem “pry”
gem “rake”
gem “bcrypt”
gem “sinatra-flash”
group :test do
gem “rspec”
gem “capybara”
gem “coveralls”
end

Then I need to create my framework in Sinatra. I’m going to do this the easy way by running the following in the command line

$ rspec-sinatra init — app Chitter chitter.rb in 

This creates my controller, spec helper file and config.ru file, and prefills some info to save me time.

Then I create my folder structure for my app folder and spec folders, keeping the controller, the models, and the view separate.

Next thing is to set up the databases locally, I’m going to create a test, a development, and a production database using PSQL. Then I’ll link it up to my app so they know which one to use.

Now — time to write some failing tests!

First one is:

feature “post peep” do
scenario “a user can post a new peep” do
visit ‘/peeps/new’
fill_in ‘peep’, with: ‘This is my first peep!’
fill_in ‘user’, with: “Dan”
click_button ‘Post peep’
expect(current_path).to eq ‘/peeps’
expect(page).to have_content(‘This is my first peep!’)
end
end

I need to do a lot of things to make it pass, but using TDD I can get the tests to show me what to do.

I got a bit excited and stopped blogging and just building — I’m a coder not a blogger! Here’s what I did to make the test pass. Editing note: I’ve updated the code with my final code, since I had quite a few bugs in it.

In my controller:

get ‘/peeps/new’ do
erb :’/peeps/new’
end
post '/peeps' do
new_peep = Peep.create(peep: params[:peep], user_id: session[:user_id])
new_peep.save
redirect :'/peeps'
end
get ‘/peeps’ do
@all_peeps = Peep.all
erb :’peeps/index’
end

Then in my model

class Peep
include DataMapper::Resource
property :id, Serial
property :peep, String
belongs_to :user
end

and then create two views. To post the new one:

<form action='/peeps' method='post'>
<label for='peep'> Peep!</label> <input type='text' name='peep'>
<input class='button' type='submit' value='Post peep'>
</form>and when I get redirected to views:

Note that I’ve put reverse in there before the each — this is because I want to see them by newest first.

<ul id='peeps'>
<% @all_peeps.reverse.each do |show_peeps| %>
<p>
<li><%= show_peeps.peep %> => <%= show_peeps.user.name %>
<% end %>
</ul>

This has been surprisingly easy so far… But that is a good thing!!!

Now I’m going to create a users class, so that people can sign in. I should write a failing test for this.

First, in my web_helper.rb file, I’m going to define sign_up (since I’ll use it a lot)

def sign_up(name: 'test', email: 'test@test.com', password: 'password', password_confirmation: 'password' )
visit '/users/new'
expect(page.status_code).to eq(200)
fill_in :name, with: name
fill_in :email, with: email
fill_in :password, with: password
fill_in :password_confirmation, with: password_confirmation
click_button 'Sign up'
end

and then my actual test:

require ‘./spec/web_helper’
feature 'User signs up' do
scenario 'A user can sign up' do
expect { sign_up }.to change(User, :count).by(1)
expect(page).to have_content('Welcome, test!')
expect(User.first.email).to eq('test@test.com')
end

And then test drive the development of this. First error I get is that I don’t get status 200, I get 404 since the page doesn’t exist. So I create the page and link it in the controller. Then I get “Capybara::ElementNotFound Unable to find field :username, because I have nothing on my view. So I’ll go and create that. I then do a ton of validation, and write error messages for issues like the following:

  • Username and email required
  • Email must be valid
  • Email must be unique
  • Password and password confirmation must match

Then I create the functionality for the user to sign in, and validate this against the database of users who have signed up. Then sign out, which is a lot harder than I expected.

The relevant code I’ve got to do this is:

Controller

get ‘/users/new’ do
@user = User.new
erb :’/users/new’
end
post ‘/users’ do
@user = User.create(name: params[:name], email: params[:email], password: params[:password], password_confirmation: params[:password_confirmation])
if @user.save
session[:user_id] = @user.id
session[:user_name] = @user.name
redirect ‘/peeps/new’
else
flash.now[:errors] = @user.errors.full_messages
erb :’users/new’
end
end
get ‘/sessions/new’ do
erb :’sessions/new’
end
post ‘/sessions’ do
user = User.authenticate(params[:email], params[:password])
if user
session[:user_id] = user.id
redirect to(‘/peeps’)
else
flash.now[:errors] = [‘The email or password is incorrect’]
erb :’sessions/new’
end
end
delete ‘/sessions’ do
flash.keep[:notice] = ‘See you next time!’
session[:user_id] = nil
redirect to ‘/sessions/new’
end
helpers do
def current_user
@current_user ||= User.get(session[:user_id])
end
end

Model

require “data_mapper”
require “dm-postgres-adapter”
require ‘bcrypt’
class User
include DataMapper::Resource
property :id, Serial
property :name, String, required: true
property :email, String, required: true, unique: true
property :password_digest, Text
has n, :peep
attr_reader :password
attr_accessor :password_confirmation
validates_confirmation_of :password
validates_format_of :email, as: :email_address
def password=(password)
@password = password
self.password_digest = BCrypt::Password.create(password)
end
def self.authenticate(email, password)
user = first(email: email)
if user && BCrypt::Password.new(user.password_digest) == password
user
else
nil
end
end
end

Views

<form action=’/users’ method=’post’>
<label for=’name’> What should we call you?</label> <input type=’text’ name=’name’ value=’<%= @user.name %>’ required>
<br>
<label for=’email’> What is your email address? </label> <input type=’email’ name=’email’ value=’<%= @user.email %>’ required>
<br>
<label for=’password’> What will your password be?</label> <input type=’password’ name=’password’ required>
<br>
<label for=’password_confirmation’> Please confirm your password.</label> <input type=’password’ name=’password_confirmation’ required>
<br>
<input class=’button’ type=’submit’ value=’Sign up’>
</form>

Layout

<!DOCTYPE html>
<html>
<head>
<title> Chitter </title>
<nav>
<ul>
<li> <a href="/">View peeps</a> </li>
<li> <a href='/peeps/new'> New Peep</li>
<li> <a href="/users/new"> Sign up</a></li>
<li> <a href="/sessions/new"> Sign in</a></li>
</ul>
</nav>
</head>
<body>
<% if current_user %>
Welcome, <%= current_user.name %>!
<form action='/sessions' method='post'>
<input type='hidden' name='_method' value='delete'>
<input type='submit' name='Sign out' value='Sign out'>
</form>
<% end %>
<% if flash[:errors] && !flash[:errors].empty? %>
Please see the error(s) below:
<ul id='errors'>
<% flash[:errors].each do |error| %>
<li> <%= error %> </li>
<% end %>
</ul>
<% end %>
<% if flash[:notice] %>
<div id='notice'> <%= flash[:notice] %> </div>
<% end %>
<%= yield %>
</body>
</html>

I then went on to display the username next to the peep. I totally now understand why they’d recommended we build it with the user signing in first, because this was crazy annoying to change later. I hate to delete and create a new database, but I got it to work in the end. Note — the code posted above is the working code that does this. Not what I had there before.

The biggest issue I’d had was with the one to many relationship believe it or not. I ended up learning that to set it up in the Peep class, I needed to write:

belongs_to :user

and in the user class, I just needed

has n, :peep

These 28 characters cost me a good 2 hours…

Now it’s pretty much done! It needs a ton of styling, but the minimum viable product is there!

Deploying to Heroku is easy, just set up an account, get the command line tools then in your repo run:

$ heroku create [whatever_you_want_to_name_it]

then push the code to Gihub as normal and push to heroku

$ git push heroku [branch_name]:master

Last step (which I’d forgotten about and it gave me a big headache) is to tell heroku to use postgresql

$ heroku addons:create heroku-postgresql:hobby-dev

Then it all works.

Here’s my super basic web app!

I’ll fix it up a bit, and make the CSS a bit nicer, but at this stage I’m just happy to be done.

Here’s the obligatory pictures saying my pull request is ok!

Editing note: This blog post makes it sound easy and quick. This was in fact about 10 hours of work over two days, but I’m super happy with it anyway.