Showing Detailed Information in the Browser
Your bookstore is starting to come along. You have an index that lists all the books in your database by title and author. This is great, but what about the other information you’ve stored about your books? It would be useful to see how much a book costs, or how many are available.
Let’s show this information.
Let’s start by looking at your application’s routes again.
-
Open Terminal and go to your
bookstore
directory. -
Run
rake routes
.So far we’ve worked with the first row in the routes table. We used the
/books
path with theBooksController
index
action to list all your application’s books.books GET /books(.:format) books#index
Now that we want to show detailed book information we’ll need to use a different route.
book GET /books/:id(.:format) books#show
This time, we’ll use the
/books/:id
path with theBooksController
show
action to show a book’s information.
In the /books/:id
path, we can take advantage of :id
to get a book by it’s id.
What’s that? You didn’t know a book could have an id? Let’s take a look.
-
Go back to Terminal and start
rails console
. -
In the console, run
Book.first
to get the first book from your database.Take a look at the output. It might be hard to see, but at the beginning there’s an id.
#<Book id: 1, title: "why's (poignant) Guide to Ruby", ... >
The first book in your database has an id of 1.
-
I wonder what id your second book will have…
Run
Book.second
and look at the output.#<Book id: 2, title: "Oh, the Places You'll Go!", ... >
The second book in your database has an id of … 2!!!
By default, every book you create will be given a unique id, and it will be given an id in sequential order.
-
Exit the console.
How will the /books/:id
path work in action?
-
Go back to Terminal and start your application’s web server by running
rails server
. -
:id
can be replaced with specific ids. Since we know your first book’s id is 1, we can go to/books/1
.Open your browser and go to http://localhost:3000/books/1.
-
An unknown action error?! Why does that look familiar… 😉
You’re trying to access the
BooksController
show
action, but it doesn’t exist. Let’s make it!
-
Open your text editor and go to
app/controllers/books_controller.rb
. -
After the index action, add the following:
def show end
-
Save your changes, and go to http://localhost:3000/books/1 again.
-
Now you get a different error:
ActionController::UnknownFormat (BooksController#show is missing a template for this request format and variant.
We’ve seen this error too. You added the
show
action, but it doesn’t have a template to go with it.
1
2
3
4
5
6
7
8
class BooksController < ApplicationController
def index
@books = Book.all
end
def show
end
end
Let’s add the missing template.
-
Go back to Terminal and stop your application’s web server by running
Ctrl-C
. -
Now, run
touch app/views/books/show.html.erb
to create the empty template. -
Restart your application’s web server by running
rails server
and revisit http://localhost:3000/books/1.Remember, a blank page means no errors. 😆
Now that we have the
BooksController
show
action rendering a template, we’re ready to show a book’s details.
How do we know which book we’re showing?
Before you can show a book’s details, you have to figure out which book’s details to show. How can we figure out which book to show?
When you visit http://localhost:3000/books/1, you send a request to your application’s web server. Thanks to Rails routing, the book id (1) is included in that request.
Started GET "/books/1" for ::1 at 2016-12-26 16:12:07 -0500
Processing by BooksController#show as HTML
Parameters: {"id"=>"1"}
Rendering books/show.html.erb within layouts/application
Rendered books/show.html.erb within layouts/application (0.3ms)
Completed 200 OK in 266ms (Views: 263.1ms | ActiveRecord: 0.0ms)
It’s tucked in there, but the id is inside the parameters hash. See it? Try to find it in your server’s output.
Parameters: {"id"=>"1"}
Let’s see how we can access the parameters hash in your BooksController
show
method.
-
Open your text editor and go to
app/controllers/books_controller.rb
. -
Request parameters are available in all your application’s controllers as
params
.Inside the
show
method, add the following code:@id = params[:id]
1
2
3
4
5
6
7
8
9
class BooksController < ApplicationController
def index
@books = Book.all
end
def show
@id = params[:id]
end
end
By defining @id
in the BooksController
show
method, it’s available in the BooksController
show
template.
-
Open
app/views/books/show.html.erb
in your text editor and add the following:This book's id is <%= @id %>.
-
Save your changes.
1
This book's id is <%= @id %>.
Now that the BooksController
show
template is wired up to render something, what do you expect to see if you visit http://localhost:3000/books/1?
Do you have an idea? Let’s verify it!
-
Go to http://localhost:3000/books/1.
What do you see?? Was it what you were expecting?
Did reality meet your expecations?
The page renders “This book’s id is 1.” becuase you made a request from your browser with book id 1 (http://localhost:3000/books/1).
You’re able to render the id because you grabbed it from the request parameters and assigned it to @id
inside the BooksController
show
action.
What happens if you go to http://localhost:3000/books/2?
What about http://localhost:3000/books/3?
The new book id is rendered on every request because you wired up the template to show the requested book’s id.
Finding a Book
Now that we have the book id for the book we want to show, we can use it to get that book from your application’s database.
We’ve used a few methods to get books from the database. We’ve used Book.all
to get all the books, and we’ve used Book.first
to get the first book.
But how do we get a book by it’s id? Let’s explore on the rails console
.
-
Go back to Terminal and quit your application’s web server by running
Ctrl-C
. Then, start therails console
. -
Now try running the following code:
Book.find(3)
What did it return? It returned the book in your application’s database with id 3.
#<Book id: 3, title: "1984", author: "George Orwell", price_cents: 0, created_at: "2016-12-26 15:51:15", updated_at: "2016-12-26 15:51:15", quantity: 200>
-
What do you get when you run
Book.find(1)
orBook.find(5)
?You get the book with that id.
-
What happens if you do
Book.find(100)
?ActiveRecord::RecordNotFound: Couldn't find Book with 'id'=100
An error!
Book.find
returns an error when it doesn’t find a book with the given book id.So we can use
Book.find
to get books from your application’s database with a given id. If a book doesn’t exist with that id, we get anActiveRecord::RecordNotFound
error. -
Exit the console and restart your application’s web server.
Let’s use our new knowledge of Book.find
to get and show the requested book in the BooksController
show
method.
-
In your text editor, open the
BooksController
. -
We’ve already grabbed the requested book id and set it to
@id
. Let’s use that to find the requested book.Add the following line to the end of the
show
method:@book = Book.find(@id)
-
Save your changes and go to http://localhost:3000/books/1.
Did you notice anything different? I hope not, because we didn’t update the template 😝
1
2
3
4
5
6
7
8
9
10
class BooksController < ApplicationController
def index
@books = Book.all
end
def show
@id = params[:id]
@book = Book.find(@id)
end
end
-
Now, open the
BooksController
show
template (app/views/books/show.html.erb
). -
Add the following line to the end of the file
This book is called <%= @book.title %>
-
Save your changes and go back to http://localhost:3000/books/1.
1
2
This book's id is <%= @id %>.
This book is called <%= @book.title %>.
Yay! You got the request book and showed some of its information.
Unfortunately, it’s kinda smashed together on the page.
Let’s use a definition list to clean it up.
-
Open
app/views/books/show.html.erb
in your text editor and delete everything in the file.Yes, EVERYTHING.
There’s nothing wrong with what we’ve done so far, but adding the definition list is easier from scratch.
-
Now, add the following code to the file:
<dl> <dt>Id</dt> <dd><%= @book.id %></dd> <dt>Title</dt> <dd><%= @book.title %></dd> </dl>
This is an HTML definition list with a couple of terms: “Id” and “Title”. The terms are defined with values for the given book.
Save your changes and go to http://localhost:3000/books/1 to see the results.
1
2
3
4
5
6
7
<dl>
<dt>Id</dt>
<dd><%= @book.id %></dd>
<dt>Title</dt>
<dd><%= @book.title %></dd>
</dl>
-
Now that we have a definition list with the book's id and title, update it to include the rest of the book's information (author, price_cents, and quantity).
-
What did you come up with? Your solution should look something like this:
<dl> <dt>Id</dt> <dd><%= @book.id %></dd> <dt>Title</dt> <dd><%= @book.title %></dd> <dt>Author</dt> <dd><%= @book.author %></dd> <dt>Price Cents</dt> <dd><%= @book.price_cents %></dd> <dt>Quantity</dt> <dd><%= @book.quantity %></dd> </dl>
-
Update your solution to match this solution.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dl>
<dt>Id</dt>
<dd><%= @book.id %></dd>
<dt>Title</dt>
<dd><%= @book.title %></dd>
<dt>Author</dt>
<dd><%= @book.author %></dd>
<dt>Price Cents</dt>
<dd><%= @book.price_cents %></dd>
<dt>Quantity</dt>
<dd><%= @book.quantity %></dd>
</dl>
Price Cents?
The books details look great, but doesn’t “Price Cents” look kinda weird?
Do you ever wonder how much something would cost in pennies? Really?! I thought I was the only one!
For most people, it would be easier to see the book’s price in dollars. Fortunately, Rails has our back.
Rails provides a number_to_currency
method that prints a number as currency.
Let’s see what happens when you use it.
-
Reopen
app/views/books/show.html.erb
and change the “Price Cents” definition from<dd><%= @book.price_cents %></dd>
to
<dd><%= number_to_currency(@book.price_cents) %>
-
Save your changes and revist http://localhost:3000/books/1.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dl>
<dt>Id</dt>
<dd><%= @book.id %></dd>
<dt>Title</dt>
<dd><%= @book.title %></dd>
<dt>Author</dt>
<dd><%= @book.author %></dd>
<dt>Price Cents</dt>
<dd><%= number_to_currency(@book.price_cents) %></dd>
<dt>Quantity</dt>
<dd><%= @book.quantity %></dd>
</dl>
Looks great, right?!
Except why’s (poignant) Guide to Ruby doesn’t cost $100.00. We set the book’s price_cents
to 100, and 100 cents is $1.00.
number_to_currency
formats a number as a dollar amount, but the number has to be in dollars.
We need to first convert price_cents
to dollars before we can really use number_to_currency
.
-
Reopen
app/views/books/show.html.erb
and change the “Price Cents” definition from<dd><%= number_to_currency(@book.price_cents) %></dd>
to
<dd><%= number_to_currency(@book.price_cents / 100.0) %></dd>
To convert from cents to dollars, we can simply divide by 100.
(Actually we have to divide by 100.0 because floating point math is fun)
Then, the converted value is used by
number_to_currency
. -
You can also go ahead and change the definition term from “Price Cents” to “Price”.
-
Save your changes and try looking up a few of your books in your browser.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dl>
<dt>Id</dt>
<dd><%= @book.id %></dd>
<dt>Title</dt>
<dd><%= @book.title %></dd>
<dt>Author</dt>
<dd><%= @book.author %></dd>
<dt>Price</dt>
<dd><%= number_to_currency(@book.price_cents / 100.0) %></dd>
<dt>Quantity</dt>
<dd><%= @book.quantity %></dd>
</dl>
We’re making really good progress! Anyone can visit your bookstore to see what books you have, and they can learn a little about those books.
I love that I can see what books you have and how much they cost, but what if I’ve never heard of one of your books before??
Let’s add book descriptions to help give just a little more info about your books.
Adding descriptions to books
We can add descriptions to books by adding a description
column to the books
table.
Do you remember how we added columns to your application’s database tables?
We’ve done that through migrations. We created a migration to add the books
table, and we changed that migration to add new columns.
Now that you’re bookstore application is coming along, we don’t want to go back and edit past migrations. Editing past migrations can compromise the data you’ve worked so hard to collect!
But how will we add the description
column to the books
table??
A new migration!
-
Go back to Terminal and stop your application’s web server by running
Ctrl-C
. -
Now, run
rails generate migration add_description_to_books
.This creates a new timestamped migration file
db/migrate/TIMESTAMP_add_description_to_books.rb
.
-
Open your new migration in your text editor.
-
Inside the
change
method, add the following line:add_column :books, :description, :text
With this change, we’re setting up the migration to add a column to the
books
table. The new column will be nameddescription
and it will store any amount oftext
. -
Save your changes.
1
2
3
4
5
class AddDescriptionToBooks < ActiveRecord::Migration[5.0]
def change
add_column :books, :description, :text
end
end
-
Now, go back to Terminal.
Run
rake db:migrate
to run your new migration against your application’s database.
Let’s take a look at how this changes your bookstore.
-
In Terminal, start
rails console
, get your first book and assign it to a variable calledmy_first_book
.my_first_book = Book.first
Does the book look different?
At the end, you should see a nil description.
#<Book id: 1, title: "why's (poignant) Guide to Ruby", ..., description: nil>
description
is nil because you just added it. -
You can give
my_first_book
a description by runningmy_first_book.update(description: "YOUR INSPIRING DESCRIPTION")
Where YOUR INSPIRING DESCRIPTION is…YOUR INSPIRING DESCRIPTION.
(Don’t worry about your description being inspiring 😉)
-
After you’ve added YOUR INSPIRING DESCRIPTION to
my_first_book
, you can runmy_first_book.description
to see the new description. -
Go ahead and add descriptions for the of rest your books. Your application’s database should have seven books, which means you have six descriptions to write.
-
When you’re done, exit the
rails console
and restart your application’s web server by runningrails server
.
-
Now that your books have descriptions, you can share them with the WORLD!
Reopen
app/views/books/show.html.erb
and add the book’s description to the end of the definition list.
-
What did you come up with? You should’ve added the following lines:
<dt>Description</dt> <dd><%= @book.description %></dd>
-
Save your changes and view your beautiful descriptions in your browser.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dl>
<dt>Id</dt>
<dd><%= @book.id %></dd>
<dt>Title</dt>
<dd><%= @book.title %></dd>
<dt>Author</dt>
<dd><%= @book.author %></dd>
<dt>Price</dt>
<dd><%= number_to_currency(@book.price_cents / 100.0) %></dd>
<dt>Quantity</dt>
<dd><%= @book.quantity %></dd>
<dt>Description</dt>
<dd><%= @book.description %></dd>
</dl>
Links!
Now you have a lovely books index and lots of individual book views, but there is no relationship between the two. Let’s link them!
Since you have a books index with a little bit of book information and book views lots of specific information, it makes sense to link from the books index to specific book pages.
If a visitor to your bookstore is really excited to see a book on the index, they could click on that book to get more detailed information about it.
We’ll update the index so book titles link to their book’s page.
In HTML, you add links using anchor elements (<a>
tags). Anchor elements link to other pages by their href
attribute.
Fortunately, Rails gives us a convience method to abstract away some of that work.
-
In your text editor, open
app/views/books/index.html.erb
. -
Change the list item that renders book titles and authors to this:
<li> <%= link_to(book.title, book_path(book)) %> by <%= book.author %> </li>
Rails gives us a
link_to
method we can use to create anchor elements.First, you give it the text for the link. In this case, we want to use the book’s title as the link text.
Then, you give it the path for the link. We want the link to go to
/books/:id
, but we passed itbook_path(book)
.book_path(book)
is another Rails method. Long story short, it generates/books/:id
for the given book. -
Save your changes and go to http://localhost:3000/books. Try clicking all the links!
When you’re done, stop your application’s web server.
1
2
3
4
5
6
7
8
9
<h1>Welcome to My Super Rad Bookstore!</h1>
<ul>
<% @books.each do |book| %>
<li>
<%= link_to(book.title, book_path(book)) %> by <%= book.author %>
</li>
<% end %>
</ul>