Create a New Record Using a Form

Displaying A Form

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:

  1. provide the user with a form to collect the details of the new post
  2. receive that submitted form data and add it as a new post to our collection of posts

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:

  1. a <form> with method and action attributes
  2. <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.

Creating A Post

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.

  • The first thing we do in 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.
  • Then we see those four values passed in as arguments to 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>.
  • And SQLite3 handles our ids 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:

  1. the 302 status code
  2. the 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>:

  1. POST /create_post
    • creates post in DB
    • HTTP response: 302 Location: /list_posts
  2. 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!

Linking to Our New Post Page

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>

Finishing Our Form

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:

  1. when you click the label in the browser, the focus will go to the input field
  2. visually impaired users that depend on screen reading software will be able to use your form, since screen readers depend on these attributes to associate form inputs with their labels