Updating Books
You have so many books in your bookstore! đ
What would happen if you suddenly sold every copy of a book? What about if the price changes?
Youâd have to open rails console
to change your bookâs data. That doesnât sound very great.
What if you could update books in your browser? Wouldnât that be a little easier?
Letâs make it so you can can update books in the browser.
-
Open Terminal and go to your
bookstore
directory. Then, runrake routes
. -
Now that we want to edit and update books, a couple of rows in the routing table should stand out.
First, take a look at the row with
edit_book
.edit_book GET /books/:id/edit(.:format) books#edit
The path to
edit_book
is/books/:id/edit
. Doesnât that look a little familiar? Youâve been going to/books/:id
to see the details for a given book. Now, youâll go to/books/:id/edit
to edit the given book. -
Start your applicationâs web server and go to http://localhost:3000/books. From the index, click the link for whyâs (poignant) Guide to Ruby.
Your browser should have URL of
http://localhost:3000/books/1
in its address bar. Edit the URL so itâshttp://localhost:3000/books/1/edit
to visit the edit path for whyâs (poignant) Guide to Ruby. Now, try going to the new URL.
You got an error, right?
Good.
Can you guess why youâre seeing an Unknown action
error?
Youâre getting an Unknown action
error because your BooksController
doesnât have an edit
method. Letâs add it.
-
Open the
BooksController
in your text editor. Add theedit
method to the end of the controller.def edit end
-
Try going to http://localhost:3000/books/1/edit again.
What do you think will happen?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class BooksController < ApplicationController
def index
@books = Book.all
end
def show
@id = params[:id]
@book = Book.find(@id)
end
def new
@book = Book.new
end
def create
Book.create(params.require(:book).permit(:title, :author, :price_cents, :quantity, :description))
redirect_to books_path
end
def edit
end
end
Did you guess youâd see an ActionController::UnknownFormat
error? Me either. đ
However, you might have guessed that the request was going to error. You added the edit
action to the BooksController
, but it doesnât have a template to render.
Letâs add it.
-
Go back to Terminal and stop your applicationâs web server.
-
Run
touch app/views/books/edit.html.erb
to create the edit book template. -
Restart your applicationâs web server and revisit http://localhost:3000/books/1/edit.
A blank page has never looked better!
In the edit template, weâll take advantage of forms again. Weâll use them to gather new book data.
Before we add the form to the template, letâs make some of the existing book data available.
-
Open the
BooksController
and add the following line inside theedit
method:@book = Book.find(params[:id])
(Doesnât that look a lot like the
show
method?) -
Save your changes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class BooksController < ApplicationController
def index
@books = Book.all
end
def show
@id = params[:id]
@book = Book.find(@id)
end
def new
@book = Book.new
end
def create
Book.create(params.require(:book).permit(:title, :author, :price_cents, :quantity, :description))
redirect_to books_path
end
def edit
@book = Book.find(params[:id])
end
end
The edit book form is going be similiar to the new book form.
Remember what the new book form looked like?
<%= form_for(@book) do |f| %>
<ul>
<li>
<%= f.label :title %>
<%= f.text_field :title %>
</li>
<li>
<%= f.label :author %>
<%= f.text_field :author %>
</li>
<li>
<%= f.label :price_cents %>
<%= f.number_field :price_cents %>
</li>
<li>
<%= f.label :quantity %>
<%= f.number_field :quantity %>
</li>
<li>
<%= f.label :description %>
<%= f.text_area :description %>
</li>
</ul>
<%= f.submit %>
<% end %>
Letâs start the edit book form by adding a field to edit the bookâs title.
-
Open
app/views/books/edit.html.erb
in your text editor and add the following:<%= form_for(@book) do |f| %> <ul> <li> <%= f.label :title %> <%= f.text_field :title %> </li> </ul> <% end %>
-
Save your changes and revist http://localhost:3000/books/1/edit.
Whoa! The title is already filled-in!
In the BooksController
edit
method, you found the requested book in your database and assigned it to @book
. When you called form_for
in app/views/books/edit.html.erb
, you passed it @book
.
Since @book
is a book in your database with a title, the title text_field
was populated with @book
âs title.
(Magic)
-
Update the form so it has fields for all the book attributes: title, author, price_cents, quantity, and description.
Use the new book form in
app/views/books/new.html.erb
as an example.
-
Howâs your form look? It should look like this:
<%= form_for(@book) do |f| %> <ul> <li> <%= f.label :title %> <%= f.text_field :title %> </li> <li> <%= f.label :author %> <%= f.text_field :author %> </li> <li> <%= f.label :price_cents %> <%= f.number_field :price_cents %> </li> <li> <%= f.label :quantity %> <%= f.number_field :quantity %> </li> <li> <%= f.label :description %> <%= f.text_area :description %> </li> </ul> <% end %>
-
Update your solution to match this solution. Save your changes and revisit http://localhost:3000/books/1/edit.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<%= form_for(@book) do |f| %>
<ul>
<li>
<%= if.label :title %>
<%= f.text_field :title %>
</li>
<li>
<%= f.label :author %>
<%= f.text_field :author %>
</li>
<li>
<%= f.label :price_cents %>
<%= f.number_field :price_cents %>
</li>
<li>
<%= f.label :quantity %>
<%= f.number_field :quantity %>
</li>
<li>
<%= f.label :description %>
<%= f.text_area :description %>
</li>
</ul>
<% end %>
Now you can edit all your book data, but thereâs no way to save your changes.
We need to add a submit
button so you can submit your changes via the form.
-
Open
app/views/books/edit.html.erb
and add asubmit
button as the last line inside theform_for
block.<%= f.submit %>
This will add a button to your form labeled âUpdate Bookâ. Magic
-
Save your changes and revisit http://localhost:3000/books/1/edit. Try making a change and submitting the form.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<%= form_for(@book) do |f| %>
<ul>
<li>
<%= f.label :title %>
<%= f.text_field :title %>
</li>
<li>
<%= f.label :author %>
<%= f.text_field :author %>
</li>
<li>
<%= f.label :price_cents %>
<%= f.number_field :price_cents %>
</li>
<li>
<%= f.label :quantity %>
<%= f.number_field :quantity %>
</li>
<li>
<%= f.label :description %>
<%= f.text_area :description %>
</li>
</ul>
<%= f.submit %>
<% end %>
Weâve been through this drill beforeâŚ
-
Open the
BooksController
and add anupdate
method to the end of it. -
Save your changes and revisit http://localhost:3000/books/1/edit. Try making a change to the book and submitting the form.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class BooksController < ApplicationController
def index
@books = Book.all
end
def show
@id = params[:id]
@book = Book.find(@id)
end
def new
@book = Book.new
end
def create
Book.create(params.require(:book).permit(:title, :author, :price_cents, :quantity, :description))
redirect_to books_path
end
def edit
@book = Book.find(params[:id])
end
def update
end
end
Nothing happened!
The form is being submitted to the BooksController
update
method, and the method isâŚempty. I think Iâd be more surprised if something did happen. đ
The update
method should probably do something. Maybe it should update the book.
-
First, letâs find the book youâre trying to update.
Open the
BooksController
and add the following line insided theupdate
method:book = Book.find(params[:id])
Weâve done this in a few of the
BooksController
methods.Now that we have the book, how will we
update
it?Youâve updated books before. Do you remember when you added descriptions to all your books? How did you do it?
You did something along the lines of
my_first_book.update(description: "Woooo")
.You called the
update
method on each of your books to give them descriptions. Weâll useupdate
again to make the requested changes. -
We could simply do
book.update(params[:book])
, but that wonât work because of strong parameters.Instead, add the following line to the end of the
BooksController
update
method.book.update(params.require(:book).permit(:title, :author, :price_cents, :quantity, :description))
-
Save your changes, revisit http://localhost:3000/books/1/edit, make some changes, and submit that form!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class BooksController < ApplicationController
def index
@books = Book.all
end
def show
@id = params[:id]
@book = Book.find(@id)
end
def new
@book = Book.new
end
def create
Book.create(params.require(:book).permit(:title, :author, :price_cents, :quantity, :description))
redirect_to books_path
end
def edit
@book = Book.find(params[:id])
end
def update
book = Book.find(params[:id])
book.update(params.require(:book).permit(:title, :author, :price_cents, :quantity, :description))
end
end
Where are my changes?!
Nothing happened, right?
Go to http://localhost:3000/books/1, and make sure to refresh the page.
Your change is there!
Donât believe me? Try making another change?
How are changes happening when it looks like the form isnât being submitted?
The form is being submitted to the BooksController
update
method, and the requested changes are being made to the book. But thatâs where the update
method ends. It looks like nothing happened because thereâs no visual queue that something happened.
After a book is updated, letâs redirect to that bookâs details. That way, we can see the changes.
-
At the end of the
BooksController
update
method, add the following line:redirect_to book_path(book)
-
Save your changes, revisit http://localhost:3000/books/1/edit, and try making another change. When you submit your changes, you should be redirected to the first bookâs details. Keep an out to see your changes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class BooksController < ApplicationController
def index
@books = Book.all
end
def show
@id = params[:id]
@book = Book.find(@id)
end
def new
@book = Book.new
end
def create
Book.create(params.require(:book).permit(:title, :author, :price_cents, :quantity, :description))
redirect_to books_path
end
def edit
@book = Book.find(params[:id])
end
def update
book = Book.find(params[:id])
book.update(params.require(:book).permit(:title, :author, :price_cents, :quantity, :description))
redirect_to book_path(book)
end
end
Yay! In addition to adding books from the browser, you can now edit books too.
Before we move on, letâs take a look at cleaning up some stuff. Letâs start with the BooksController
.
In both the create
and update
methods of the BooksController
, youâre authorizing and getting the same params.
def create
Book.create(params.require(:book).permit(:title, :author, :price_cents, :quantity, :description))
...
end
...
def update
...
book.update(params.require(:book).permit(:title, :author, :price_cents, :quantity, :description))
...
end
Fortunately, thereâs a convention to help manage this shared bit of code.
-
Open the
BooksController
in your text editor, and add the following code to the end of it:def book_params params.require(:book).permit(:title, :author, :price_cents, :quantity, :description) end
By defining a new
book_params
method, we have one place to deal with getting and authorizing book params. -
Now, update the
create
method so it usesbook_params
. ChangeBook.create(params.require(:book).permit(:title, :author, :price_cents, :quantity, :description))
to
Book.create(book_params)
-
Make the same change to the
update
method. -
Save your changes and try creating and editing books. Everything should still work the same as it did before these changes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class BooksController < ApplicationController
def index
@books = Book.all
end
def show
@id = params[:id]
@book = Book.find(@id)
end
def new
@book = Book.new
end
def create
Book.create(book_params)
redirect_to books_path
end
def edit
@book = Book.find(params[:id])
end
def update
book = Book.find(params[:id])
book.update(book_params)
redirect_to book_path(book)
end
def book_params
params.require(:book).permit(:title, :author, :price_cents, :quantity, :description)
end
end
Letâs make one more change to improve the edit workflow.
Right now, you can only edit books if you know the edit_book_path
URL. Letâs make this a little user-friendly by adding a link to the edit page from the book show page.
-
Open
app/views/books/show.html.erb
in your text editor. Add the following line to the end of the file:<%= link_to "Edit book", edit_book_path(@book) %>
This will create a link with text âEdit bookâ. It will link to the
edit_book_path
for@book
which will evaluate to/books/:id/edit
.Adding the link here works really well because we already have the book we want to edit as
@book
. -
Save your changes and visit a few books. Clicking the âEdit bookâ link should take you to the edit page for the book youâre viewing.
-
When youâre done exploring, stop your applicationâs web server.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<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>
<%= link_to "Edit book", edit_book_path(@book) %>