List and Show Records

Listing Our Posts

For our blog app, we want to first display a list of all post titles. And now that we have a database with a few posts in it, we'll actually have something to display here!

So to get started, we'll recall that with our hello_world action we needed three things:

  1. a route
  2. an action
  3. a view

We'll need each of those here as well, so let's kick things off by defining a route for /list_posts:

config/routes.rb

Rails.application.routes.draw do
  get '/list_posts' => 'application#list_posts'
end

the application#list_posts action points to:

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  def list_posts
    connection = SQLite3::Database.new 'db/development.sqlite3'
    connection.results_as_hash = true

    posts = connection.execute("SELECT * FROM posts")

    render 'application/list_posts', locals: { posts: posts }
  end
end

Inside our list_posts action we see our DB initialization logic from before, followed by the SELECT query to grab all of our posts as hashes. After that, we're just passing those along as locals to the view.

A view which isn't defined yet, so let's do that next:

<!-- app/views/application/list_posts.html.erb -->

<html>
  <body>

    <div class="posts">
      <% posts.each do |post| %>
        <div class="post">

          <h2 class="title">
            <%= post['title'] %>
          </h2>

          <small class="meta">
            <span class="author">by <%= post['author'] %> -</span>
            <em class="created_at"><%= post['created_at'] %></em>
          </small>

        </div>
        <hr />
      <% end %>
    </div>

  </body>
</html>

There's quite a bit of markup here, but most of it should be familiar, with the exception of this:

<% posts.each do |post| %>
  <!-- ... -->
<% end %>

Here we see how we're able to iterate over an array inside of an ERB view. Above we loop over posts with posts.each, building a <div> for each post. (This post variable is available anywhere before the <% end %> for the block.)

An important thing to note here is the use of <% %> (without an =) as opposed to <%= %>. This allows us to execute Ruby without placing the return value into the page.

Besides that, you can see that we simply place the proper post values into different locations in the post <div> with lines like <%= post['title'] %>.

And with that, if you visit /list_posts, you'll see the titles of our three posts, along with their respective authors' names and dates of creation.

Show A Post To The User

Now that we have /list_posts working, let's implement something for /show_post/:id, which is where users will go to read a given post.

First, we'll need a route:

### config/routes.rb ###

Rails.application.routes.draw do
  get '/list_posts'    => 'application#list_posts'
  get '/show_post/:id' => 'application#show_post'
end

You can see here we set up a URL pattern to capture id as a parameter that we'll use in our controller action.

Then we'll need a show_post action:

### app/controllers/application_controller.rb ###

class ApplicationController < ActionController::Base

  # ...

  def show_post
    connection = SQLite3::Database.new 'db/development.sqlite3'
    connection.results_as_hash = true

    post = connection.execute("SELECT * FROM posts WHERE posts.id = ? LIMIT 1", params['id']).first

    render 'application/show_post', locals: { post: post }
  end
end

In this show_post action, we first need to get a post from the database by a particular ID. We again run some SQL against the DB with connection.execute, but this time we call .first on the result. The reason here is that connection.execute is always going to return an array, even if it's only ever going to contain a single element, as is the case here with the LIMIT 1.

The way we use this with the database is interesting though, so let's take a look:

connection.execute("SELECT * FROM posts WHERE posts.id = ? LIMIT 1", params['id'])

For our purposes, this line has the same effect as:

connection.execute("SELECT * FROM posts WHERE posts.id = #{params['id']} LIMIT 1")

The ? character in the original statement is a placeholder that will be replaced by the value of the second parameter, which is params['id']. This is a safer way to include user's input (in this case, from the URL) in a SQL statement. Rails makes sure that the statement is safe. The second statement is not safe because we included user's input directly in the statement. This technique is called protection against SQL Injection Attacks. We'll get to this topic later in the book.

But getting back to the last line of our action, we see that we simply pass our post along to our show_post view. Speaking of, let's define that now:

<!-- app/views/application/show_post.html.erb -->

<html>
  <body>

    <div class="post">
      <h2 class="title">
        <%= post['title'] %>
      </h2>

      <small class="meta">
        <span class="author">by <%= post['author'] %> -</span>
        <em class="created_at"><%= post['created_at'] %></em>
      </small>

      <p class="body"><%= post['body'] %></p>
    </div>

    <br />

  </body>
</html>

This view is basically the same as our list_posts view, except that it doesn't have a loop and does render the post body.

Hit /show_post/1 and you'll see the post details from before, plus the post body.

Links

Finally, let's make some links between these two pages:

<!-- app/views/application/show_post.html.erb -->

<html>
  <body>

    <!-- link to list of posts -->
    <a href="/list_posts">Back to Posts</a>

    <div class="post">
      <!-- ... -->
    </div>

    <br />

  </body>
</html>
<!-- app/views/application/list_posts.html.erb -->

<html>
  <body>

    <div class="posts">
      <% posts.each do |post| %>
        <div class="post">

          <h2 class="title">
            <!-- link to post -->
            <a href="/show_post/<%= post['id'] %>"><%= post['title'] %></a>
          </h2>

          <!-- ... -->

        </div>
        <hr />
      <% end %>
    </div>

  </body>
</html>

The link from the show view to the list view is a simple, static link:

<a href="/list_posts">Back to Posts</a>

While the one in the loop in the list view is more involved:

<a href="/show_post/<%= post['id'] %>"><%= post['title'] %></a>

Here we dynamically build the href using the post ID and then set the link text to the post title for each post.

Extract Shared Logic for DB Connection

You might have noticed in show_post that we repeated the same two lines to connect to the database that we used in list_posts. Let's move this logic to its own connection method in the controller and adjust both actions to make use of it:

class ApplicationController < ActionController::Base
  def list_posts
    posts = connection.execute("SELECT * FROM posts")

    render 'application/list_posts', locals: { posts: posts }
  end

  def show_post
    post = connection.execute("SELECT * FROM posts WHERE posts.id = ? LIMIT 1", params['id']).first

    render 'application/show_post', locals: { post: post }
  end

  private

  def connection
    db_connection = SQLite3::Database.new 'db/development.sqlite3'
    db_connection.results_as_hash = true
    db_connection
  end
end

This is great because if we need to change things about our database connection later, we can just make the change in a single place. Additionally, if any other actions need to make use of a database connection (they will), then they'll be able to make use of connection too.