Rails and Postgres

Guess what we are going to do today? Make a Rails app using PostgreSQL! Hooray! So what is our project going to be about? Lets make a list of songs and link some hott tunes up in here. CD into the parent directory of where you would like this project directory to live… rails is going to be doing quite a bit of work for us, starting with creation of that project directory.

$ rails new music_playlist --database=postgresql --skip-test-unit --skip-spring

So since we are going to be using posgreSQL for our database and rspec and capybara for our testing, we need to add those two flags listed above when we create the project. Otherwise, we will need to delete the test unit stuff and change our database later, might as well be tidy and do that now. Lately, spring has veen more problematic than problem solving, so we will skip that too. Is rails done makin stuff yet?

$ tree music_playlist

Whoa! Rails made tons of files!

$ cd music_playlist
$ rails server

Rails server will start the local server, and an alias for this is rails s. Open localhost:3000 and it says the database doesn’t exist. Let’s make one!

$ rake db:create

Check out localhost now… happy appy! To look in our app for the database name and so forth, check out config/database.yaml. Somewhere buried in those comments is the database information. Feel free to delete those comments. You can also check out your database using the PostgreSQL server… accessing that either by $ rails db or the regular postgres route (see previous blog post).

Let’s add the gems rspec-rails and capybara to our Gemfile, and be sure to put them in a test and development group like we did last time. Look up the gems in RubyGems for the current version.

$ bundle install
$ rails generate rspec:install
$ rake spec

Remember rspec --init in our non-rails rspec projects? In Rails we use rails generate rspec:install. This will create a spec directory for us and place a auto-generated spec_helper.rb file inside of that directory. And we can use rails g or rails generate to run that rpsec:install command. The $ rake spec will tell us that everything is loading properly.

To allow us to use capybara and capybara DSL in our testing, we need to add the line require 'cabybara/rails' after the require File.expand_path("../../config/environment", __FILE__) of spec_helper.rb file. Let us run the spec again to make sure everything is still loading properly and we didn’t break anything… and then let’s commit this monster of a beginning!

$ rake spec
$ git init
$ git status

OMG so many files! This is a special exception to the “never $ git add .” rule

$ git add .
$ git commit -m "Initial commit. Rspec, Capybara gems added, running Postgres."

Now it’s time to get this party started! Create a new directory features in the spec directory, and then create the file song_spec.rb inside that directory containing the following code:

require 'spec_helper'

feature 'User can manage a music playlist' do
  scenario 'User is welcomed on the homepage' do
    visit '/'
    expect(page).to have_content('Welcome to PlaylistLand')
  end
end

Now we will start running rspec with rake to help us direct the development of our app. After each addition of code, RUN RSPEC AGAIN to follow along and see what we should do next. Start understanding those sweet messages your dear computer is sending you.

$ rake spec

Looks like we need to define a route. First, we edit the config/routes.rb file. Perhaps we will start by deleting all those comments! Then we add the following within the Rails.application.routes.draw do...end.

get '/' => 'dashboard#index'

That line of code is going to direct a few things… the name of the new controller file we are about to create will be dashboard_controller.rb, the name of the class defined within that file will be DashboardController, and there will be a method defined within that class named index (which will point to index.html.erb). Let us go ahead and add a new file in the app/controller folder named dashboard_controller.rb with the following code:

class DashboardController < ApplicationController
  def index
  end
end

The “missing template” indicates that rake spec wants a view. Add a new dir and file app/views/dashboard/index.html.erb with the h1 text “Welcome to PlaylistLand”. Rake spec again. Yeah!

$ git status
$ git add -N app/ spec/
$ git add -p
$ git commit -m "User is Welcomed on the homepage"

Well done! Now we will change the test to reflect direction the app will take… in spec/features/song_spec.rb, change the spec within the scenario block to the following:

scenario 'User can add titles, links to the homepage' do
  visit '/'
  song_link = 'Welcome to PlaylistLand'
  title = "Mokadem - 'Mokadem' EP"
  url = 'https://soundcloud.com/thump/sets/mokadem-mokadem-ep'
  click_on song_link
  click_on 'Add new Jam'
  fill_in 'Title', with: title
  fill_in 'URL', with: url
  click_on 'Queue!'
  expect(page).to have_content(title)
  expect(page).to have_content(url)
end

Rake spec tells us that it cannot find the link. Let’s turn that h1 into a link.

<h1><a href="/song">Welcome to PlaylistLand</a></h1>

Looks like we need to add a get route for /song. Add this again to config/route.rb

get '/song' => 'song#index'

Rspec is now looking for the SongController, wich we will put inside app/controllers/song_controller.rb. We will add this to the file:

class SongController < ApplicationController
  def index
  end
end

And now another view… song/index.html.erb. Now rake spec is looking for the “Add new jam” link. Let’s add that to the song index!

<h1>PlaylistLand</h1>
<hr>
<a href="/song/new">Add new Jam</a>

Now we add that to the routes file.

get '/song/new' => 'song#new'

And now we need to add the action ‘new’ to the Song Controller.

def new
end

Next we are told that we are missing a template. Lets add new.html.erb to our views/song folder. This will contain the form for our data entry:

<form action="/song" method="post">
  <input type="text" placeholder="Title" name="title">
  <input type="text" placeholder="URL" name="url">
  <input type="submit" value="Queue!">
</form>

Now we need to add a post route to the routes.rb file.

  post '/song' => 'song#create'

Let’s add that method to the song controller.

def create
end

Now we are missing a template. However, we really don’t want a new view for this, we just want to submit that new information in the form to the database, and then render it on the song index page. So let’s add some stuff to that method. If you really want rspec to push you to do this, put in that redirect line first, and run your test again.

song = Song.new
song.title = params[:title]
song.url = params[:url]
song.save
redirect_to '/song'

Rake spec tells us that we have an uninitialized constant SongController::Song. Check it out in localhost, too. Oh no, what’s that? An invalid authenticity token? Go to app/controller/application_controller.rb and comment out line 4 protect_from_forgery with: :exception. Refresh the browser. Ah, now our spec and browser agree. We will add a new file app/models/song.rb with the following code:

class Song < ActiveRecord::Base
end

Rake spec is now telling us that the relation “songs” does not exist. No songs table! Shall we create one?

$ rails g migration CreateSong title:string url:string

This command creates a migration in the db/migrate folder with a timestamp(UTC). Let’s open that up and ensure that matches our spec… within the create_table :songs do |t| ... end you will have the following code:

t.string :title
t.string :url

Remember that we have that title and url coming in from our form on the /song/new page? Let’s run that migration!

$ rake db:migrate

Nice job! Feel free to again poke around in the database from the command line again at this point. Another cool thing is to use the rails console. You can execute ActiveRecord commands directly on the database, for example to create rows in the table. It’s kinda like IRB but works on your development database.

$ rails console

Ok, back to our program. Rspec is now back to looking for the text “Mokadem - ‘Mokadem’ EP”. So now we need to get that database to iterate in the index.html.erb file. We start that by defining some local variables… within song_controller.rb, within def index...end:

@songs = Song.all

Within song/index.html.erb:

<% @songs.each do |song| %>
  <%= song.title %> | <%= song.url %><br>
<% end %>

Rspec says green! Let’s check that baby out in the browser, and then commit!

$ git status
$ git add -N app/ db/
$ git add -p
$ git status
$ git commit -m "User can add song titles and URLs, see songs listed on the song homepage"

Nice! Next up in the CRUD cycle is Update… shall we? Lets have it where when you click the name of the song, you go to the song page, and there there is an edit button which takes you to a page to edit the title or url. Lets add this test to our song_spec.

scenario 'User can update songs' do
  old_title = 'Touch'
  title = 'Holy Other - Touch'
  url = 'https://soundcloud.com/holyother/touch'
  visit '/song'
  click_on 'Add new Jam'
  fill_in 'Title', with: old_title
  fill_in 'URL', with: url
  click_on 'Queue!'
  click_on old_title
  expect(page).to have_content(old_title)
  expect(page).to have_content(url)
  click_on 'Edit'
  fill_in 'Title', with: title
  fill_in 'URL', with: url
  click_on 'Update'
  expect(page).to have_content(title)
  expect(page).to have_content(url)
end

Looks like rake spec wants us to make the title link first. Dig in! We will wrap that song title on the index like so:

  <a href="/song/<%= song.id %>"><%= song.title %></a> | <%= song.url %><br>

Rspec tells us we need a new get route.

get '/song/:id' => 'song#show'

Next, show cannot be found for SongController. We put that in the song_controller.rb.

def show
end

Looks like it’s time to create another view. Do you know what it will be called and where it will go? It will be app/views/song/show.html.erb. Rspec now is looking for that title on the show page, and we are going to need to hand that show page a single song. We can do this by adding the following code to that show method:

def show
  @song = Song.find(params[:id])
end

Now we need pass that to the show page to display the title, url, and then add the edit link.

<h3><%= @song.title %> | <%= @song.url %> | <a href="/song/<%= @song.id %>/edit">Edit</a></h3>

And add the get route…

get '/song/:id/edit' => 'song#edit'

Add the method to the SongController…

def edit
end

We will have to pass in the song information indexed by the title link, so add @song = Song.find(params[:id]) to the edit method. Then add the view. You know where it goes. I’ll just tell you even though you totally know by now… in views/song/edit.html.erb. Running rspec tells us that we need that field “Title”. Let’s make that form for editing the Song information!

<form action="/song/<%= @song.id %>" method="post">
  <input type="hidden" name="_method" value="put"/>
  <input type="text" id="Title" value="<%= @song.title %>" name="title"/>
  <input type="text" id="URL" value="<%= @song.url %>" name="url"/>
  <input type="submit" value="Update"/>
</form>

Whoa, there is some weird stuff going on there. Ok, we want to edit our record… there isn’t really a method in browsers to do that. So we sort of make one by changing our method to “put” in that second line. And we would be jerks if we made people add all that information again on the edit page, so we can just fill out that form for the user and allow them to actually edit it. Now we need to add that put route in the routes file.

put '/song/:id' => 'song#update'

Aaand, add that to our SongController…

def update
end

That is going to be a redirect_to '/song'… what are we missing? Looks like we need to collect that information from the form and actually update the database. Our update method in the SongController will finally look like this:

def update
  song = Song.find(params[:id])
  song.title = params[:title]
  song.url = params[:url]
  song.save
  redirect_to '/song'
end

Whaaat? I almost forgot what green looked like. Check it in the browser, and commit this lovely beast.

$ git status
$ git add -N app/views/song/edit.html.erb app/views/song/show.html.erb
$ git add -p
$ git status
$ git commit -m "User can edit song title and url"

Awesome! We only have delete to go! Let’s write that test.

scenario 'User can delete songs' do
  title = 'Epic Rap Battle - Geek v Nerd'
  url = 'http://www.youtube.com/watch?v=2Tvy_Pbe5NA'
  visit '/song'
  click_on 'Add new Jam'
  fill_in 'Title', with: title
  fill_in 'URL', with: url
  click_on 'Queue!'
  click_on title
  click_on 'Over it!'
  expect(page).to have_no_content(title)
  expect(page).to have_no_content(url)
end

Looks like we start with the “Over it!” We add this to the show page:

<form action="/song/<%= @song.id %>" method="post">
  <input type="hidden" name="_method" value="delete"/>
  <input type="submit" value="Over it!">
</form>

And we add the route…

delete 'song/:id' => 'song#destroy'

And the method in the SongController. We know this will be a redirect, so add that after running rspec again.

def destroy
  redirect_to '/song'
end

Now we need to actually delete that content from our database table. In that destroy method, we again find the song (just like we did in update) and then just use the method destroy like so:

def destroy
  song = Song.find(params[:id])
  song.destroy
  redirect_to '/song'
end

What does rspec say? Browser check? Wonderful! Lets do our final commit for this wee project. Nice Job!

$ git status
$ git add -p
$ git status
$ git commit -m "User can delete songs."

Check it out on GitHub!