Now that we can see a list of posts and read a specific post, let's give the user a means to create a new post.
This will involve two parts:
Let's first look at how to display a form that we can use to collect user input. We'll start by defining a route, action, and view to serve the new post form to the client:
### config/routes.rb ###
Rails.application.routes.draw do
# ...
get '/new_post' => 'application#new_post'
end
### app/controllers/application_controller.rb ###
class ApplicationController < ActionController::Base
# ...
def new_post
render 'application/new_post'
end
end
<!-- app/views/application/new_post.html.erb -->
<html>
<body>
<form method="post" action="/create_post">
<label>Title</label>
<input name="title" type="text" />
<br /> <br />
<label>Body</label>
<textarea name="body"></textarea>
<br /> <br />
<label>Author</label>
<input name="author" type="text" />
<br /> <br />
<input type="submit" value="Create Post" />
</form>
</body>
</html>
We add another route to send GET
requests for the /new_post
path to the application#new_post
action.
Then we define that new_post
action, which then renders the new_post
view.
And finally we define a new_post
view, which has a few new pieces. Namely:
<form>
with method
and action
attributes
<input>
s with name
attributes
The <form>
attributes are hugely important to us because method
determines the HTTP request method (a.k.a. verb) used for form submission and action
determines the request path.
<form method="post" action="/create_post">
Does this pair sound familiar?
These two pieces of information are what we use to define a route. So here in the <form>
we decide where to submit to, i.e. send a request. Then in our routes.rb
we'll route requests of that type to a post action, which we'll define next.
So what kind of route will we need to respond to this request?
If you peek ahead, you'd see that in the next step we end up with:
post '/create_post' => 'application#create_post'
But coming back to our view, you'll also see a handful of <input>
s inside the <form>
that look something like this:
<input name="title" type="text" />
The type
attribute is used to determine the kind of <input>
(here, text
results in a single-line text input), but name
has a special purpose.
When a form like this is submitted with a POST
request, it sends the form data to the server in the body of the HTTP request as POST data. Much like query string parameters, POST data is essentially just a collection of key-value pairs. The values here are obvious: string values for the post title, body, and so on.
But what determines the keys for the POST data?
The name
attribute on an <input>
.
This is a crucial detail to understand, because Rails will place these POST data pairs into params
for us, and we'll need to know which keys to look for.
So in the next step, when we go looking for a post's title on a request resulting from this form submit, we'll find it in params['title']
, because inside the <form>
we have an <input>
for it with name="title"
.
Speaking of the next step, we're actually done with our form for now! Hit /new_post
and you'll see our new post form.
Now that we have a form for a new post, let's define the route and the action it submits to:
### config/routes.rb ###
Rails.application.routes.draw do
# ...
post '/create_post' => 'application#create_post'
end
### app/controllers/application_controller.rb ###
class ApplicationController < ActionController::Base
# ...
def create_post
insert_query = <<-SQL
INSERT INTO posts (title, body, author, created_at)
VALUES (?, ?, ?, ?)
SQL
connection.execute insert_query,
params['title'],
params['body'],
params['author'],
Date.current.to_s
redirect_to '/list_posts'
end
end
We start implementing post creation by defining a route for our form to be submitted to. This route is quite similar to those before it, except that it uses the post
method, as it's looking to match on POST
requests.
Next we have our create_post
action.
create_post
is execute some SQL to INSERT
the new post into the posts
table. Since our SQL query string is multiline, we define it using a heredoc (the <<-SQL ... SQL
syntax above), and inside we see four ?
s for the values of our four post attributes.
connection.execute
, following the query argument. We also see that we get these values out of params
, as in params['title']
. These values make their way into params
based on the name
attributes of the <input>
s in our new_post
<form>
.
id
s automatically, so we don't need to worry about setting those.
Then, with our new post successfully added to the database, we simply respond to the client by redirecting them back to the list of posts. Since the new post is now a row in the posts
table, list_posts
will include its title when it renders.
Redirecting is accomplished with the Rails-provided redirect_to
method. We have this available to us for the same reason we have render
: ApplicationController
inherits from ActionController::Base
.
To see what goes on for a redirect, let's look again at our response
object, but this time, after our redirect_to
call:
# assuming we're inside the create_post action above...
redirect_to '/list_posts'
require 'pry'; binding.pry;
response.body
# => "<html><body>You are being <a href=\"http://localhost:3000/list_posts\">redirected</a>.</body></html>"
response.content_type # => nil
response.status # => 302
response.headers
# => {
# # ...
# "Location"=>"http://localhost:3000/list_posts"
# }
response.headers['Location']
# => "http://localhost:3000/list_posts"
Here the response body is set to a message about redirection and the content type is nil
, but because this is a redirect, we really don't care about these values.
Instead, the two key parts are:
302
status code
Location
header
These two values are what make the redirect happen. The reason is that the 302
status code means Found
, as in "we found the thing you requested, but it's somewhere else", and the value found in the Location
header is used to explain where.
So the 302
tells the browser that it needs to redirect, and the Location
header explains where it should redirect to. By passing a path string to redirect_to
in our action above, we set both of these values to tell the user's browser to go to /list_posts
.
It's also interesting to note that issuing a redirect causes the browser to make a total of two requests when the user submits the new post <form>
:
POST /create_post
302
Location: /list_posts
GET /list_posts
And speaking of submitting the form, we're now able to create a new post from /new_post
, thanks to the route and action above!
Now that we can create posts from /new_post
, let's link to it from /list_posts
:
<!-- app/views/application/list_posts.html.erb -->
<html>
<body>
<div class="posts">
<!-- ... -->
</div>
<!-- link to new post -->
<a href="/new_post">New Post</a>
</body>
</html>
And back to /list_posts
from /new_post
:
<!-- app/views/application/new_post.html.erb -->
<html>
<body>
<form method="post" action="/create_post">
<!-- ... -->
</form>
<br />
<!-- link back to the list of post -->
<a href="/list_posts">Back to Posts</a>
</body>
</html>
Before we move on, there are a couple attributes we should add to the fields in our <form>
:
<html>
<body>
<form method="post" action="/create_post">
<label for="title"> Title</label>
<input id="title" name="title" type="text" />
<br /> <br />
<label for="body"> Body</label>
<textarea id="body" name="body"></textarea>
<br /> <br />
<label for="author"> Author</label>
<input id="author" name="author" type="text" />
<br /> <br />
<input type="submit" value="Create Post" />
</form>
<br />
<a href="/list_posts">Back to Posts</a>
</body>
</html>
As you can see above, each <input>
and <textarea>
gets an id
, and their corresponding <label>
gets a for
attribute set to the value of the id
. This is how we associate a <label>
with its input field.
This has two primary benefits: