Creating Books Through the Browser
So far you’ve created books on the rails console. It works, but it’s not the best experience. In most web applications, you enter data through…the web.
Your bookstore is a web application, so let’s treat it like one. Let’s set up your application so you can create books from your browser.
-
Open Terminal and go to the
bookstoredirectory. -
Now, run
rake routes.We’ve worked through a couple of the books routes. First, we used the
indexaction to list all your application’s books. Then, we used theshowaction to show details for a given book.Now, we’ll use the
newaction to create a new book. -
The path for the
newaction is/books/new.new_book GET /books/new(.:format) books#newLet’s try going to that path.
$ pwd
/Users/awesomesauce/Projects/bookstore
$ rake routes
Prefix Verb URI Pattern Controller#Action
books GET /books(.:format) books#index
POST /books(.:format) books#create
new_book GET /books/new(.:format) books#new
edit_book GET /books/:id/edit(.:format) books#edit
book GET /books/:id(.:format) books#show
PATCH /books/:id(.:format) books#update
PUT /books/:id(.:format) books#update
DELETE /books/:id(.:format) books#destroy-
In Terminal, start your application’s web server by running
rails server. -
Now, try going to http://localhost:3000/books/new.
-
You got an error, but doesn’t it look familiar?
Why you think you got an Unknown action error?
$ rails server
=> Booting Puma
=> Rails 5.0.0.1 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
The error message gives you a hint as to why you’re getting an Unknown action error.
The action 'new' could not be found for BooksController
When you make a request to /books/new, your request gets routed to the BooksController new action. What does your BooksController look like? Does it have a new action?
The BooksController doesn’t have a new action because we haven’t defined it yet.
-
In your text editor, open the
BooksController(app/controllers/boooks_controller.rb). -
At the end of the
BooksControlleradd thenewmethod.def new end -
Save your changes, and revisit http://localhost:3000/books/new.
1
2
3
4
5
6
7
8
9
10
11
12
13
class BooksController < ApplicationController
def index
@books = Book.all
end
def show
@id = params[:id]
@book = Book.find(@id)
end
def new
end
end

Another error, but it should also look familiar.
BooksController#new is missing a template for this request format and variant.
You’ve added the new method to the BooksController, but you haven’t created the new action’s template.
Let’s add it!
-
Go back to terminal and stop your application’s web server by running
Ctrl-C. -
Now, run
touch app/views/books/new.html.erbto create thenewaction’s template. -
Restart your application’s web server and revisit http://localhost:3000/books/new. What do you see?
A blank page! 🎉
$ rails server
...
^CExiting
$ touch app/views/books/new.html.erb
$ rails server
=> Booting Puma
=> Rails 5.0.0.1 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup optionsA Little About Forms
Data is usually added to web applications through forms.
You probably have never noticed them, but forms are everywhere on the internet. When you login to a site, you enter your credentials into a form. When you post a status on Facebook, you enter your status into a form. When you’re Googling for programming resources, you enter your search terms into a form.
After you add your data to a form, you submit it by clicking a submit button. For example, a login form might have a submit button that says “Login”.
We’re going to use forms to create new books in your application.
-
In your text editor, open
app/views/books/new.html.erband add the following code:<%= form_for(@book) do |f| %> <% end %>form_foris a method provided by Rails. It provides a consistent interface to build forms inside Rails applications.Here, we’re passing
@booktoform_forbecause we want to create a form for a book.form_fortakes a block as its last argument. We haven’t done anything interesting inside the block, but that’ll change soon 😉 -
Save your changes and revisit http://localhost:3000/books/new.
1
2
<%= form_for(@book) do |f| %>
<% end %>

Hmm…an error.
You’re seeing an ArgumentError in Books#new. There’s a lot going on in the error, but the message has a helpful hint.
First argument in form cannot contain nil or be empty
Remember the code we used to start the form?
<%= form_for(@book) do |f| %>
@book is the first argument in the form, so the error is telling us @book must be nil or empty.
How could @book be nil?
-
Open the
BooksControllerin your text editor and take a look at thenewmethod.Is
@bookdefined inside in the new method?def new endIt’s nowhere to be found! That explains that error 😅
1
2
3
4
5
6
7
8
9
10
11
12
13
class BooksController < ApplicationController
def index
@books = Book.all
end
def show
@id = params[:id]
@book = Book.find(@id)
end
def new
end
end
Let’s fix that error.
-
Inside the
BooksControllernewmethod, add the following line:@book = Book.new@bookis set to be a new book because we want to create a form for new books. -
Save your changes and revisit http://localhost:3000/books/new.
No errors! But we still have a blank page. Let’s start building out that form.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
end
-
In your text editor, open
app/views/books.new.html.erb. -
Inside the
form_forblock, add the following:<ul> <li> <%= f.label :title %> <%= f.text_field :title %> </li> <li> <%= f.label :author %> <%= f.text_field :author %> </li> </ul>We’re putting your form elements inside an unordered list. The form will have two fields, one for title and one for author.
f.labeldefines labels for each field.f.text_fieldcreates a text input field for each field. -
Save your changes and revisit http://localhost:3000/books/new.
Can you match the code you added to the different elements on the page?
1
2
3
4
5
6
7
8
9
10
11
12
13
<%= form_for(@book) do |f| %>
<ul>
<li>
<%= f.label :title %>
<%= f.text_field :title %>
</li>
<li>
<%= f.label :author %>
<%= f.text_field :author %>
</li>
</ul>
<% end %>
-
Your new book form has fields for title and author.
Try adding a field for the price. Remember, we named the field
price_cents.
-
What did you come up with? If you followed the pattern used for the other two fields, you probably came up with something like this:
<li> <%= f.label :price_cents %> <%= f.text_field :price_cents %> </li>That works well, but
price_centsisn’t a text field. It’s a number.To make it a number field, simply change
<%= f.text_field :price_cents %>to
<%= f.number_field :price_cents %> -
Your full solution should look like this:
<li> <%= f.label :price_cents %> <%= f.number_field :price_cents %> </li>Update your solution, save your changes, and revisit http://localhost:3000/books/new.
Does the
price_centsfield differ from other fields on the page?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<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>
</ul>
<% end %>
We have a couple more fields to add to the form. We need fields for quantity and description.
-
Add
quantityto your form as anumber_field. -
Add
descriptionto your form as atext_area.(
text_areadiffers fromtext_fieldby offering more space for text - which is useful for long descriptions.)
-
What does your solution look like? Your complete form 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 and save your changes.

Now you have a beautiful form full of fields. You can enter so much book data! 😍
But there’s no way for you to save that data in your application’s database 😞
Don’t worry! We can fix that!
-
At the end of the
form_forblock`, add the following line:<%= f.submit %>This will add a submission button to your form.
-
Save your changes and revisit http://localhost:3000/books/new.
f.submitgenerates a button labeled “Create Book”.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<%= 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>
</ul>
<%= f.submit %>
<% end %>

-
Now that you have a form with a shiny “Create Book” button, why don’t you try creating a book?
Fill out the form and click “Create Book”.
-
Error? ERROR?!

You’re getting an
Unknown actionerror that says “The action ‘create’ could not be found for BooksController”.Huh?
We’ve been working with the
BooksControllernewaction? How did we end up with an error for thecreateaction?

Take a look at what happened in your application’s web server when you clicked “Create Book”.
Started POST "/books" for ::1 at 2016-12-27 14:32:50 -0500
AbstractController::ActionNotFound (The action 'create' could not be found for BooksController):
Before the error happened, the server received a POST request to the /books path. This request happened when you clicked the “Create Book” button.
Your form’s data was submitted as a POST request to the /books path.
Real interesting, right? Let’s take a look at your application routes again.
-
Go back to Terminal and stop your application’s web server by running
Ctrl-C. Then, runrake routes. -
Take a look at the second row of the routing table.
POST /books(.:format) books#createDoes that look familiar? 😉
POST requests to
/booksget sent to theBooksControllercreateaction!It looks like we were destined to reach the
BooksControllercreateaction, but how did the form know to go there?By default, a
form_forwith a new record will be wired up to POST requests to its matchingcreateaction.In our case, we setup
form_forwith a new book. Therefore, the form was setup to POST to theBooksControllercreateaction.Does this make sense to you? If it does, I’m impressed. I’m having a hard time understanding it all. 😅
-
Don’t worry if you don’t understand everything that is going on. It will make more sense over time.
The key take away here is the
newbook form gets submitted to theBooksControllercreate action. -
Restart your application’s web server.
$ rails server
...
^CExiting
$ rake routes
Prefix Verb URI Pattern Controller#Action
books GET /books(.:format) books#index
POST /books(.:format) books#create
new_book GET /books/new(.:format) books#new
edit_book GET /books/:id/edit(.:format) books#edit
book GET /books/:id(.:format) books#show
PATCH /books/:id(.:format) books#update
PUT /books/:id(.:format) books#update
DELETE /books/:id(.:format) books#destroy
$ rails server-
Open the
BooksControllerin your text editor. -
Add a
createmethod to the end of the controller.def create end -
Save your changes and try adding a book again.
Nothing happens! Hey, it’s better than an error…
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
end
endThe BooksController create action is going to be responsible for using your form data to create a new book.
Take a look at the request parameters that are sent when you submit the new book form.
Started POST "/books" for ::1 at 2016-12-27 15:29:52 -0500
Processing by BooksController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"AOZpk66Fc20R/9xHVhxZLiSwsQj29isG2ohi9gS6TKXT0PUP3n9hYJQrPpY8iSx6uessf9Sgsd3Uv3rEuB/TvQ==", "book"=>{"title"=>"The Cat in the Hat", "author"=>"Dr. Seuss", "price_cents"=>"500", "quantity"=>"1000", "description"=>"That crazy cat!"}, "commit"=>"Create Book"}
Inside the parameters hash, there’s a “book” key with its own hash.
"book"=>{"title"=>"The Cat in the Hat", "author"=>"Dr. Seuss", "price_cents"=>"500", "quantity"=>"1000", "description"=>"That crazy cat!"}
This is the server output for the book I was trying to add, but your server output should look similar.
(Unless you were trying add The Cat in the Hat. Then, it should look pretty much the same…)
To get the book data that’s POSTed to the server, we need to access the book key in the params hash.
-
Add the following code to your
BooksControllercreatemethod:Book.create(title: params[:book][:title], author: params[:book][:author], price_cents: params[:book][:price_cents], quantity: params[:book][:quantity], description: params[:book][:description])We’re using
Book.createto create a new book. The new book’s attributes are being set with values from theparamshashbookkey. -
Save you changes and try adding a book again.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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(title: params[:book][:title], author: params[:book][:author], price_cents: params[:book][:price_cents], quantity: params[:book][:quantity], description: params[:book][:description])
end
end
Nothing happened, right?
Not exactly…
-
Go to Terminal and stop your application’s web server.
-
Now, run
rails consoleand get the last book in your database.Does it look familiar?
It’s the book you created!
Although it looked like nothing happened when you clicked submit, the request made it to the
BooksControllercreateaction andBook.createwas run. -
The
createaction is almost done. Now, we just need make the experience a little better.Exit
rails consoleand restart your application’s web server.
$ rails server
...
^CExiting
$ rails console
Loading development environment (Rails 5.0.0.1)
>> Book.last
Book Load (0.5ms) SELECT "books".* FROM "books" ORDER BY "books"."id" DESC LIMIT ? [["LIMIT", 1]]
=> #<Book id: 8, title: "The Cat in the Hat", author: "Dr. Seuss", price_cents: 500, created_at: "2016-12-27 20:46:15", updated_at: "2016-12-27 20:46:15", quantity: 1000, description: "That crazy cat!">
>> exit
$ rails server-
In your text editor, open the
BooksController. -
At the end of the
createmethod, add the following line:redirect_to books_pathWith this change, you’ll be redirected to the
books_pathafter a new book is created.books_pathis a Rails helper method. It evaluates to/booksor your book index page. -
Save your changes and revisit http://localhost:3000/books/new.
Add a new book and you will be redirected to the books index. The new book will show up at the end of the index.
Magic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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(title: params[:book][:title], author: params[:book][:author], price_cents: params[:book][:price_cents], quantity: params[:book][:quantity], description: params[:book][:description])
redirect_to books_path
end
end
Cleanup
Ah, it feels like we’ve come so far from those days of creating books on the console…
What? It hasn’t been that long?
Anyways…
Things are coming together nicely. Let’s take a look at cleaning some stuff up. First, we’ll start with the BooksController.
-
Open the
BooksControllerin your text editor and take a look at thecreatemethod. -
In the first line of the
createmethod, you’re callingBook.createand setting attributes one by one from the params hash.Book.create(title: params[:book][:title], author: params[:book][:author], price_cents: params[:book][:price_cents], quantity: params[:book][:quantity], description: params[:book][:description])Do you notice a pattern in the attributes that are being set?
Every attribute that you’re setting is in
params[:book].title: params[:book][:title] author: params[:book][:author] price_cents: params[:book][:price_cents] ...Since every attribute you’re setting is in
params[:book], we can simplify that line. -
Change
Book.createfromBook.create(title: params[:book][:title], author: params[:book][:author], price_cents: params[:book][:price_cents], quantity: params[:book][:quantity], description: params[:book][:description])to
Book.create(params[:book]) -
Save your changes, revisit http://localhost:3000/books/new, and try creating a book.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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[:book])
redirect_to books_path
end
end

So…that didn’t work.
The change we made is valid, but we’re running into a Rails security feature.
Rails won’t let you set several attributes at once with data from a request. In this case, the data came from a request you generated. However, it’s not hard to imagine a malicious user sending harmful data.
Before you can set several attributes at once from request data, you have to explicitly state which attributes can be set. That’s why you’re seeing an ActiveModel::ForbiddenAttributesError - you haven’t permitted any attributes.
This security feature is called strong parameters.
-
Reopen your
BooksController. -
To use strong parameters, change
Book.createfromBook.create(params[:book])to
Book.create(params.require(:book).permit(:title, :author, :price_cents, :quantity, :description)When we want to create a book from the
paramshash, theparamshash must have abookhash. From thebookhash, we’ll get any of the permitted attributes and assign them to the new book. -
Save your changes, revisit http://localhost:3000/books/new, and try creating a book again.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
end
To create books in the browser, you’ve been going directly to http://localhost:3000/books/new. That works, but it isn’t very convenient.
Let’s add a link to the book index that will take us to http://localhost:3000/books/new.
-
Open
app/view/books/index.html.erbin your text editor. -
After the unordered list, add the following line:
<%= link_to("Add a book", new_book_path) %>We’re using the
link_tohelper to create a link. The link’s text will be “Add a book”, and it will link to the new_book_path (/books/new). -
Save your changes and go to http://localhost:3000/books/new.
You should see the new link. Clicking it should take you to the new book form.
1
2
3
4
5
6
7
8
9
10
11
<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>
<%= link_to("Add a book", new_book_path) %>

Now you can add all the books you heart desires from the comfort of your browser.
-
When you’re done adding books and basking in the glory, stop your application’s web server and give yourself a high five.
