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 Bootstrap: getbootstrap.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.
<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
- 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 (FT)
By tts-jaime
Rock n Roll App (FT)
- 1,349