Rock 'n roll app

Tonight's goals

  • Learn how to implement a one to many association
     
  • Learn how to validate inputs
     
  • Practice with Bootstrap

The basics

Stuff you already know how to do:

  • Start a new rails project called Top5
  • Generate a Main controller with an index action
  • Create a root route to the index page
  • Scaffold 2 resources
    • Artist (name: string) 
    • Song (title:string, rating:integer)

 

Add some data

relationships

This chart outlines the desired relationship between artists and songs (called entities or resources). But we're missing something...

The foreign key

the foreign key connects the song to the artist.

The data

Here's what our data tables might look like with the addition of the foreign key column.

Artist

  id           name

  1         Prince

  2         Madonna

  3         Pearl Jam

 

Song

  id            title                  rating        artist_id

  1      Like a Prayer         8                  2

  2      Vogue                        9                  2             

  3      Purple Rain             9                 1

 

update the artist view

How would we add artist id to the artist index view?

<table>
  <thead>
    <tr>
      <th>Id</th>
      <th>Name</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @artists.each do |artist| %>
      <tr>
        <td><%= artist.id %></td>
        <td><%= artist.name %></td>
        <td><%= link_to 'Show', artist %></td>
        <td><%= link_to 'Edit', edit_artist_path(artist) %></td>
        <td><%= link_to 'Destroy', artist, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

Adding a migration

let's add the artist_id column to our song table:

AddColumnToResources column_name: datatype

$ rails g migration AddArtistIdToSongs artist_id:integer 
$ rake db:migrate

Permitted params

We also have to tell the controller to allow our new column to be saved to the database.

    def song_params
      params.require(:song).permit(:title, :rating, :artist_id)
    end
controllers/songs_controller.rb

Updating views

The next step is updating our views so the artist_id can be added to the record and viewed in the browser.​

  <div class="field">
    <%= f.label :artist_id %><br>
    <%= f.number_field :artist_id %>
  </div> 
<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Rating</th>
      <th>Artist</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @songs.each do |song| %>
      <tr>
        <td><%= song.title %></td>
        <td><%= song.rating %></td>
        <td><%= song.artist_id %></td>
views/songs/_form.html.erb
views/songs/index.html.erb

one to many Association

We now have to tell the model that the primary key
and foreign key are associated

  • A song belongs to an artist

  • An artist has many songs

class Artist < ActiveRecord::Base
  has_many :songs
end
class Song < ActiveRecord::Base
  belongs_to :artist
end
app/models/song.rb
app/models/artist.rb

Voila

and just like that the artist and song are associated. How do we know?

 

Let's see what happens when we update the songs index view to display artist name instead of ID.

   <% @songs.each do |song| %>
      <tr>
        <td><%= song.title %></td>
        <td><%= song.rating %></td>
        <td><%= song.artist.name %></td>

getting an error?

We have to purge our database and start again _or_ associate each song in our db to avoid this error.

$ rake db:reset

SOngs index

Now, finally, we should see artist names instead of artist ids

SOngs show challenge

Your turn!

 Update the songs show page to show the artist name.

<p>
  <strong>Title:</strong>
  <%= @song.title %>
</p>

<p>
  <strong>Rating:</strong>
  <%= @song.rating %>
</p>

<p>
  <strong>Artist:</strong>
  <%= @song.artist.name %>
</p>
views/songs/show.html.erb

Artist options

artist ids are hard to remember. instead let's provide a dropdown of artist names when adding a song. 

 # GET /songs/new
  def new
    @song = Song.new
    @artists = Artist.all
  end

  # GET /songs/1/edit
  def edit
    @artists = Artist.all
  end
songs_controller.rb
<!-- Remove this line -->
<%#= f.number_field :artist_id %>

<%= f.select(:artist_id, @artists.collect {|a| [ a.name, a.id ] }, {:include_blank => 'Please select an artist'}) %>
songs/_form.html.erb

rating options

Let's allow users to only rate from 1 - 10.

<!-- Remove this line -->
<%#= f.number_field :rating %>

<%= f.select :rating, (1..10), {:include_blank => 'Rating'} %>
songs/_form.html.erb

Populate the database

Go ahead and add some artists and associated songs.

Artist show page

How would we update the artist show page to display a complete list of artist songs?

<p>
  <strong>Name:</strong>
  <%= @artist.name %>
</p>

<p>
 <strong>Songs:</strong>
 <ul>
  <% @artist.songs.each do |song| %>
    <li><%= song.title %></li>
  <% end %>
  </ul>
</p>
views/artists/show.html.erb

Artist index page

Carry this loop through and display the same list of associated songs on the artist index page.

 <tbody>
    <% @artists.each do |artist| %>
      <tr>
        <td><%= artist.id %></td>
        <td><%= artist.name %></td>
        <td><ul><% artist.songs.each do |song| %>
              <li><%= song.title %></li>
            <% end %>
            </ul>
        </td>
        <td><%= link_to 'Show', artist %></td>
        <td><%= link_to 'Edit', edit_artist_path(artist) %></td>
        <td><%= link_to 'Destroy', artist, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
views/artists/index.html.erb

Stretch Break

Validations - presence

Let's make sure the user enters something in each field.
We don't want nil data!

class Song < ActiveRecord::Base
  belongs_to :artist
  validates :title, presence: true
  validates :artist_id, presence: true
  validates :rating, presence: true
end
class Artist < ActiveRecord::Base
  has_many :songs
  validates :name, presence: true
end
models/song.rb
models/artist.rb

Validations - uniqueness

We also don't want duplicate song or artist names.

class Song < ActiveRecord::Base
  belongs_to :artist
  validates :title, presence: true
  validates :artist_id, presence: true
  validates :rating, presence: true
  validates :title, uniqueness: true
end
class Artist < ActiveRecord::Base
  has_many :songs
  validates :name, presence: true
  validates :name, uniqueness: true
end
models/song.rb
models/artist.rb

Validations errors

If you're getting an error it's because we need to reference @artists in the create & update actions.

def create
  @song = Song.new(song_params)
  @artists = Artist.all
...

end
controllers/songs_controller.rb
def update
  @artists = Artist.all
  respond_to do |format|
...

end

Top 5 songs

Let's do something with our homepage finally. Here we'll pull in the top 5 songs based on rating.

assets/stylesheets/application.css.erb
<h2> My Top 5 </h2>
  <% @songs.each do |song| %>
    <h2><%= song.title %></h2>
    <h3>by <%= song.artist.name %></h3>
    <p><strong>Rating:</strong> <%= song.rating %></p>	             
  <% end %>
views/main/index.html.erb
class MainController < ApplicationController
  def index
    @songs = Song.all
  end
end
controllers/main_controller.rb

This is a start. It's pulling in all songs.

 

Top 5 songs

How would we order by descending ratings (so it lists highest to lowest)?

assets/stylesheets/application.css.erb
class MainController < ApplicationController
  def index
    @songs = Song.order(rating: :desc)
  end
end
controllers/main_controller.rb
class MainController < ApplicationController
  def index
    @songs = Song.order(rating: :desc).limit(5)
  end
end

How would we print ONLY 5 RECORDS?

Bootstrap-ification

Add bootstrap styles to your site

 <!DOCTYPE html>
<html>
<head>
  <title>Top5</title>
  <%= stylesheet_link_tag "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" %>
  <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true %>

  <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
  <%= csrf_meta_tags %>
</head>
<body>

  <%= link_to "Home", root_path %> |
  <%= link_to "Songs", songs_path %> |
  <%= link_to "Artists", artists_path %>

  <%= yield %>

</body>
</html>

add a Template

Let's Grab some code from a bootstrap example page.

http://getbootstrap.com/examples/cover/​

   <div class="site-wrapper">
      <div class="site-wrapper-inner">
        <div class="cover-container">
          <div class="masthead clearfix">
            <div class="inner">
              <h3 class="masthead-brand">Cover</h3>
              <nav>
                <ul class="nav masthead-nav">
                  <li class="active"><a href="#">Home</a></li>
                  <li><a href="#">Features</a></li>
                  <li><a href="#">Contact</a></li>
                </ul>
              </nav>
            </div>
          </div>
          <div class="inner cover">
            <h1 class="cover-heading">Cover your page.</h1>
            <p class="lead">Cover is a one-page template for building simple and beautiful home pages. Download, edit the text, and add your own fullscreen background photo to make it your own.</p>
            <p class="lead">
              <a href="#" class="btn btn-lg btn-default">Learn more</a>
            </p>
          </div>
          <div class="mastfoot">
            <div class="inner">
              <p>Cover template for <a href="http://getbootstrap.com">Bootstrap</a>, by <a href="https://twitter.com/mdo">@mdo</a>.</p>
            </div>
          </div>
        </div>
      </div>
    </div>

html Template

We'll paste this into our application layout, add the yield tag, update the brand, nav and footer. (view source)

   <div class="site-wrapper">
      <div class="site-wrapper-inner">
        <div class="cover-container">
          <div class="masthead clearfix">
            <div class="inner">
              <h3 class="masthead-brand">JP's Top Jams</h3>
              <nav>
                <ul class="nav masthead-nav">
                  <li><%= link_to "Top 5", root_path %></li>
		  <li><%= link_to "Artists", artists_path %></li>
		  <li><%= link_to "Songs", songs_path %></li>
                </ul>
              </nav>
            </div>
          </div>
          <div class="inner cover">
            <%= yield %>
          </div>
          <div class="mastfoot">
            <div class="inner">
              <p>JP's Top Jams. @copy; 2015</p>
            </div>
          </div>
        </div>
      </div>
    </div>
views/layouts/application.html.erb

css Template

Now we have to snag the css styles. (View source: cover.css) 

/*
 * Globals
 */

/* Links */
a,
a:focus,
a:hover {
  color: #fff;
}

/* Custom default button */
.btn-default,
.btn-default:hover,
.btn-default:focus {
  color: #333;
  text-shadow: none; /* Prevent inheritence from `body` */
  background-color: #fff;
  border: 1px solid #fff;
}


/*
 * Base structure
 */

html,
body {
  height: 100%;
  background-color: #333;
}
body {
  color: #fff;
  text-align: center;
  text-shadow: 0 1px 3px rgba(0,0,0,.5);
}

/* Extra markup and styles for table-esque vertical and horizontal centering */
.site-wrapper {
  display: table;
  width: 100%;
  height: 100%; /* For at least Firefox */
  min-height: 100%;
  -webkit-box-shadow: inset 0 0 100px rgba(0,0,0,.5);
          box-shadow: inset 0 0 100px rgba(0,0,0,.5);
}
.site-wrapper-inner {
  display: table-cell;
  vertical-align: top;
}
.cover-container {
  margin-right: auto;
  margin-left: auto;
}

/* Padding for spacing */
.inner {
  padding: 30px;
}


/*
 * Header
 */
.masthead-brand {
  margin-top: 10px;
  margin-bottom: 10px;
}

.masthead-nav > li {
  display: inline-block;
}
.masthead-nav > li + li {
  margin-left: 20px;
}
.masthead-nav > li > a {
  padding-right: 0;
  padding-left: 0;
  font-size: 16px;
  font-weight: bold;
  color: #fff; /* IE8 proofing */
  color: rgba(255,255,255,.75);
  border-bottom: 2px solid transparent;
}
.masthead-nav > li > a:hover,
.masthead-nav > li > a:focus {
  background-color: transparent;
  border-bottom-color: #a9a9a9;
  border-bottom-color: rgba(255,255,255,.25);
}
.masthead-nav > .active > a,
.masthead-nav > .active > a:hover,
.masthead-nav > .active > a:focus {
  color: #fff;
  border-bottom-color: #fff;
}

@media (min-width: 768px) {
  .masthead-brand {
    float: left;
  }
  .masthead-nav {
    float: right;
  }
}


/*
 * Cover
 */

.cover {
  padding: 0 20px;
}
.cover .btn-lg {
  padding: 10px 20px;
  font-weight: bold;
}


/*
 * Footer
 */

.mastfoot {
  color: #999; /* IE8 proofing */
  color: rgba(255,255,255,.5);
}


/*
 * Affix and center
 */

@media (min-width: 768px) {
  /* Pull out the header and footer */
  .masthead {
    position: fixed;
    top: 0;
  }
  .mastfoot {
    position: fixed;
    bottom: 0;
  }
  /* Start the vertical centering */
  .site-wrapper-inner {
    vertical-align: middle;
  }
  /* Handle the widths */
  .masthead,
  .mastfoot,
  .cover-container {
    width: 100%; /* Must be percentage or pixels for horizontal alignment */
  }
}

@media (min-width: 992px) {
  .masthead,
  .mastfoot,
  .cover-container {
    width: 700px;
  }
}
assets/stylesheets/application.css

Edit some styles

The fixed position of the header and footer are causing some layout issues so let's comment out some styles and add a few.

assets/stylesheets/application.css.erb
@media (min-width: 768px) {
  /* Pull out the header and footer */
  .masthead {
   /* position: fixed;
    top: 0;*/
  }
  .mastfoot {
   /* position: fixed;
    bottom: 0;*/
  }
  /* Start the vertical centering */
  .site-wrapper-inner {
   /* vertical-align: middle;*/
  }

h3 { 
  font-weight: 100;
}

input[type="text"], input[type="number"]{
  color: #000;
}
assets/stylesheets/application.css.erb

Break into groups of 2

For the rest of the front end work i'd like you to break into teams of 2 and work together to spruce up this app.

assets/stylesheets/application.css.erb

add a background image

Instead of a background color let's add a background image. Try unsplash.com and find something awesome
(with dark colors).

assets/stylesheets/application.css.erb
html,
body {
  height: 100%;
  background: url(<%= asset_path "bg.jpeg" %>) no-repeat center center fixed;
  background-size: cover;
}
assets/stylesheets/application.css.erb

Also don't forget to save as .css.erb!

songs index

Let's fancy up our index page.
This is what you should be shooting for:

assets/stylesheets/application.css.erb

songs View - add button

Let's move the add button to the top and open up the link tag  in order to incorporate a bootstrap "plus' icon.

assets/stylesheets/application.css.erb
<h1>My Songs</h1>
<p><%= link_to new_song_path do %>
  <span class="glyphicon glyphicon-plus"></span> Add
  <% end %>
</p>
views/songs/index.html.erb

Songs View - add a grid

Now let's add a 2 column grid with a well inside. We'll also link the name of the song to the show page.

assets/stylesheets/application.css.erb
  <div class="row">
    <% @songs.each do |song| %>
      <div class="col-md-6">
        <div class="well">
          <h2><%= link_to song.title, song %></h2>
          <h3>by <%= song.artist.name %></h3>
          <h4><%= song.rating %></h4>
          <p>
            <%= link_to 'Edit', edit_song_path(song) %> |
            <%= link_to 'Destroy', song, method: :delete, data: { confirm: 'Are you sure?' } %>
          </p>
        </div>
      </div>
    <% end %>
  </div>
views/songs/index.html.erb

Songs - update Styles

Let's add a style so the background of our well has some transparency.

assets/stylesheets/application.css.erb
.well {
  background: rgba(0,0,0,0.4);
}
assets/stylesheets/application.css.erb

Songs View - more icons

Let's switch the edit and destroy links to icons for added awesomeness. 

assets/stylesheets/application.css.erb
  <div class="row">
    <% @songs.each do |song| %>
      <div class="col-md-6">
        <div class="well">
          <h2><%= link_to song.name, song %></h2>
          <h3>by <%= song.artist.name %></h3>
          <h4><%= song.rating %></h4>
          <hr/>
          <h4><%= link_to edit_song_path(song) do %>
            <span class="glyphicon glyphicon-pencil"></span>
          <% end %>  
          <%= link_to song, method: :delete, data: { confirm: 'Are you sure?' } do %>
             <span class="glyphicon glyphicon-trash"></span>
          <% end %>
          </h4>   
        </div>
      </div>
    <% end %>
  </div>
views/songs/index.html.erb

Songs View - stars

Instead of showing numbers for ratings let's show stars!

assets/stylesheets/application.css.erb
  <p>
    <% song.rating.times do %>
     <span class="glyphicon glyphicon-star"></span>
    <% end %>
  </p>
views/songs/index.html.erb

How would we color the stars?

assets/stylesheets/application.css.erb
.glyphicon-star {
  color: yellow;
}

Songs form

Let's edit the look of the songs form as well.

assets/stylesheets/application.css.erb
<div class="row">
  <div class="col-sm-6 col-sm-offset-3">
    <%= form_for(@song) do |f| %>
      <% if @song.errors.any? %>
        <div id="error_explanation">
          <h2><%= pluralize(@song.errors.count, "error") %> prohibited this song from being saved:</h2>

          <ul>
          <% @song.errors.full_messages.each do |message| %>
            <li><%= message %></li>
          <% end %>
          </ul>
        </div>
      <% end %>

      <div class="form-group">
        <%= f.label :name, class: "sr-only" %>
        <%= f.text_field :name, class: "form-control", placeholder: "Name" %>
      </div>

      <div class="form-group">
        <%= f.select :rating, (1..10), 
         {:include_blank => 'Rating'}, class: "form-control" %>
         
      </div> 
      <div class="form-group">
         <%= f.select(:artist_id, @artists.collect {|a| [ a.name, a.id ] }, 
         {:include_blank => 'Please select an artist'}, class: "form-control") %>
      </div>
      <div class="actions">
        <%= f.submit class: "btn btn-primary btn-lg" %>
      </div>
    <% end %>
  </div>
</div>
views/songs/_form.html.erb

Your Turn

Go ahead and snazz up the artist index and form pages.

assets/stylesheets/application.css.erb

Artist views

Should look something like this:

assets/stylesheets/application.css.erb
<div class="row">
  <div class="col-sm-6 col-sm-offset-3">
    <%= form_for(@artist) do |f| %>
      <% if @artist.errors.any? %>
        <div id="error_explanation">
          <h2><%= pluralize(@artist.errors.count, "error") %> prohibited this artist from being saved:</h2>

          <ul>
          <% @artist.errors.full_messages.each do |message| %>
            <li><%= message %></li>
          <% end %>
          </ul>
        </div>
      <% end %>

      <div class="form-group">
        <%= f.label :name, class: "sr-only" %><br>
        <%= f.text_field :name, class: "form-control", placeholder: "Name" %>
      </div>
      <div class="form-group">
        <%= f.submit class: 'btn btn-lg btn-primary' %>
      </div>
    <% end %>
  </div>
</div>
views/artists/_form.html.erb
<h1>My Artists</h1>
<p><%= link_to new_artist_path do %>
  <span class="glyphicon glyphicon-plus"></span> Add
  <% end %>
</p>

<div class="row">
  <% @artists.each do |artist| %>
   <div class="col-md-6">
      <div class="well"> 
      <h2><%= link_to artist.name, artist %></h2>
      
      <h4><%= link_to edit_artist_path(artist) do %>
            <span class="glyphicon glyphicon-pencil"></span>
          <% end %>
          
          <%= link_to artist, method: :delete, data: { confirm: 'Are you sure?' } do %>
             <span class="glyphicon glyphicon-trash"></span>
          <% end %>
          </h4> 
      </div>
    </div>
  <% end %>
</div>
 
views/artists/index.html.erb

Deleting an artist

What happens if we delete an artist
(one who has songs listed)?
 and then visit the songs page?

assets/stylesheets/application.css.erb

Delete an artist

We should probably delete all songs by an artist whenever we delete an artist.

assets/stylesheets/application.css.erb
# DELETE /artists/1.json
  def destroy
    @artist.destroy
    @artist.songs.each do |song|
      song.destroy
    end
    respond_to do |format|
      format.html { redirect_to artists_url, notice: 'Artist and artist songs 
were successfully destroyed.' }
      format.json { head :no_content }
    end
  end
controllers/artists_controller.rb

Add Paperclip

Let's add avatars to our artists! 

assets/stylesheets/application.css.erb
<% if @artist.avatar.exists? != nil %>
  <%= image_tag @artist.avatar.url(:thumb) %>
<% end %>
.well img {
  border-radius: 100px;
}

Homework

 

  • Finish going through the front-end slides
  • Tidy up any pages with tables
  • Add another page called Top 5 Artists that displays your top five artists based on rating.
  • Add a "add new artist" button on the artist show page
  • Add a "add new song" button on the song show page
  • Add some stylish numbers to all your Top lists
  • Extra Credit: Add a video url to each song and embed the artists' music video on the Top 5 song page.
assets/stylesheets/application.css.erb

Optional topics

 

  • Font Awesome Gem
  • Pagination Gem
  • Create a new page that adds a new song by the same artist 
assets/stylesheets/application.css.erb

Lab: pt 1

 

  • Scaffold a resource Item with a name and due date

  • Set the root page to be the Item index page
  • Output each item on the index page in order by date.
  • Make sure the date is output in a human readable format… ex: December 5, 2014
  • Push to Git and collaborate with your partner
assets/stylesheets/application.css.erb

Today we're going to break into teams and create a To Do list App

Lab: pt 2

 

assets/stylesheets/application.css.erb

You want more?

Rock n Roll App (PT)

By tts-jaime

Rock n Roll App (PT)

  • 1,379