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.
<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
- Add a migration for an associated cost
- rails g migration AddCostToItem cost:integer
- rake db:migrate
- Add this field to the homepage next to the chore
-
Add some style!
- Add google fonts
http://www.mrmcguire.com/10-useful-google-font-combinations-for-your-next-site/ - Add some cool CSS3 styles (shadows, rounded corners)
- Color palette generator
http://www.cssdrive.com/imagepalette/index.php
- Add google fonts
assets/stylesheets/application.css.erb
You want more?
Rock n Roll App (PT)
By tts-jaime
Rock n Roll App (PT)
- 1,416