Rock 'n roll app

Today's goals

  • Practice a one to many association
  • Practice with validating inputs
  • Practice with bootstrap
    • Learn how to implement a pre-built template
  • Build a fun, functional & awesome- looking app

The basics

Stuff you already know how to do:

  • Start a new rails project called Top5
  • Generate a Rock controller with a roll action
  • Create a root pointing to the roll page
  • Scaffold 2 resources
    • Artist (name) 
    • Song (name, rating:integer, artist_id:integer)
  • Add Bootstrapgetbootstrap.com

 

Foreign Key
Planning ahead for our association

one to many Association

Let's associate songs and artists.

  • 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

Artist options

now we can give users a dropdown of artist names when adding a song. (insead of having to choose an artist id)

 # 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 also limit users to entering ratings from 1 - 10.

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

<%= f.select :rating, ["1","2","3","4","5","6","7","8","9","10"], {:include_blank => 'Rating'} %>
songs/_form.html.erb

Populate the database

Go ahead and add some artists and associated songs.

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 :name, 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 :name, presence: true
  validates :artist_id, presence: true
  validates :rating, presence: true
  validates :name, 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 - uniqueness

If you're getting an error it's because we need to include the @artists variable in the create action.

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

end
controllers/songs_controller.rb

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

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!

create a logo

Let's add an icon to our logo as well.

try iconfinder.com and filter "free' icons.

assets/stylesheets/application.css.erb
<h3 class="masthead-brand"><%= image_tag "logo.png" %> JP's Top Jams</h3>

If the icon is black we can invert using Pixlr (a Photoshop-like online tool:  http://pixlr.com/editor

views/layouts/application.html.erb

songs index

Let's snazz up our index page.

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.name, song %></h2>
          <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 - adding artist

Since songs are associated with artists we can also show the artist name by using song.artist.name.

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>
          <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 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}".to_i.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","2","3","4","5","6","7","8","9","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 view 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

Artist show

Let's list all songs by a particular artist on the show page.

assets/stylesheets/application.css.erb
<p id="notice"><%= notice %></p>

<div class="row">
  <div class="col-md-12">
    <div class="well">
      <h1><%= @artist.name %></h1>

	<% @artist.songs.each do |song| %>
	  <h3><%= link_to song.name, song %></a></h3>
	<% end %>
    </div>
  </div>
</div>
views/artists/show.html.erb

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
<div class="row">
  <h2> My Top 5 </h2>
    <% @songs.each do |song| %>
      <div class="well well-lg">
        <h2><%= song.name %></h2>
	<h3>by <%= song.artist.name %> </h3>
	<p><% "#{song.rating}".to_i.times do |rank| %>
	  <span class="glyphicon glyphicon-star"></span>
        <% end %>
        </p>	             
     </div>
  <% end %>
</div>
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) and output only 5 records?

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

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

 

  • List all songs by an artist on the artist index page
  • Add a rating for artists and display it on the artists' index/show pages
  • Fix any odd formatting issues
  • 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?

Made with Slides.com