Skip to content

step 4: templating with ruby and ERB

Alex Naser edited this page Oct 8, 2015 · 1 revision

objectives

  • display existing array of post hashes in ERB
  • extend Ruby knowledge
    • instance variables
    • #each

tutorial

Let's link our existing, styled index.html to our posts in Ruby. (They're in an array of hashes currently.) To do this, we're going to use ERB. Let's jump in.

layout setup

First, let's add a file at views/layout.erb. This will serve as the frame for the paintings that are our different html pages. We only have one page (index.html) for now, but bear with me.

We're going to take the stuff from index.html that isn't specific to that page, throw it in layout.erb, and remove it from index.html:

<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="../../public/stylesheets/normalize.css">
    <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Roboto+Condensed:300italic,700italic,700,300">
    <link rel="stylesheet" href="../../public/stylesheets/lib.css">
    <link rel="stylesheet" href="../../public/stylesheets/app.css">
    <title>Finstagram</title>
  </head>
  <body>
  </body>
</html>

That leaves this in index.html:

<header>
  <!-- header stuff! -->
</header>
<main role="main">
  <!-- main stuff! -->
</main>

Next, we need to rename index.html to index.erb for... not exciting reasons. Not exciting, but necessary.

Now we have to change our references to our local stylesheets since our requests (via the <head> in layout.erb) are coming from a different place:

<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="stylesheets/normalize.css">
    <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Roboto+Condensed:300italic,700italic,700,300">
    <link rel="stylesheet" href="stylesheets/lib.css">
    <link rel="stylesheet" href="stylesheets/app.css">
    <title>Finstagram</title>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

The last thing we need to do to get our index.html content displaying in the frame of layout.erb to is to indicate in layout.erb where the painting goes. We use our very first erb tag for this! Voila:

<body>
  <%= yield %>
</body>

yield just tells our renderer, "This is where the painting goes!"

Before we actually link our Ruby code to index.html, let's make sure our erb rendering is working properly, and we can see our existing index.html. In actions.rb, at the very bottom of our get '/' block, add:

erb(:index)

Here we're calling the erb method (like our humanized_time_ago method) with one argument (:index, a Symbol). We can actually omit the parentheses when we call methods, so let's make things a little sleeker:

erb :index

Okay! Let's finally check out our old site made new again. Hello old friend! Beautiful.

displaying ruby code

She may be beautiful, but she's ugly on the inside.

Still using hardcoded html? That won't be a tenable solution when Finstagram hits 1 million users. Those monkeys we hired to add each post in raw html are going to unionize and demand impossibly high salaries.

Let's use ERB! To have access to our variables in the get '/' block of actions.rb, we need to make them instance variables. There's a bunch we could talk about regarding these, but for now, we just need to know that an instance variable is designated as such by adding a @ before the name upon assignment. For our purposes?

get '/' do
  @post_shark = {
    username: "sharky_j",
    avatar_url: "http://naserca.com/images/sharky_j.jpg",
    photo_url: "http://naserca.com/images/shark.jpg",
    humanized_time_ago: humanized_time_ago(15),
    like_count: 0,
    comment_count: 1,
    comments: [{
      username: "sharky_j",
      text: "Out for the long weekend... too embarrassed to show y'all the beach bod!"
    }]
  }

  @post_whale = {
    username: "kirk_whalum",
    avatar_url: "http://naserca.com/images/kirk_whalum.jpg",
    photo_url: "http://naserca.com/images/whale.jpg",
    humanized_time_ago: humanized_time_ago(65),
    like_count: 0,
    comment_count: 1,
    comments: [{
      username: "kirk_whalum",
      text: "#weekendvibes"
    }]
  }

  @post_marlin = {
    username: "marlin_peppa",
    avatar_url: "http://naserca.com/images/marlin_peppa.jpg",
    photo_url: "http://naserca.com/images/marlin.jpg",
    humanized_time_ago: humanized_time_ago(190),
    like_count: 0,
    comment_count: 1,
    comments: [{
      username: "marlin_peppa",
      text: "lunchtime! ;)"
    }]
  }

  [@post_shark, @post_whale, @post_marlin].to_s

  erb(:index)
end

Simple, right? Now that we have access to the three posts, let's use them with ERB in index.erb. Pretty much all we're going to do for now is access hash variables using Ruby in between ERB tags.

Remember our yield call in layout.erb?

<%= yield %>

Notice those tags around yield? Those tell the renderer, "Print this Ruby! Not HTML!" Let's use that concept to display hash values instead of hardcoded html in index.erb. Our first post should now look like this:

<article class="post">
  <div class="user-info">
    <img src="<%= @post_shark[:avatar_url] %>" alt="<%= @post_shark[:username] %>">
    <h2><%= @post_shark[:username] %></h2>
    <h3><%= @post_shark[:humanized_time_ago] %></h3>
  </div>
  <a class="photo" href="#">
    <img src="<%= @post_shark[:photo_url] %>" alt="post from <%= @post_shark[:username] %>">
  </a>
  <div class="actions">
    <%= @post_shark[:like_count] %> likes
    <span class="comment-count"><%= @post_shark[:comment_count] %> comment</span>
  </div>
  <ul class="comments">
    <li>
      <p>
        <%= @post_shark[:comments][0][:username] %>: <%= @post_shark[:comments][0][:text] %>
      </p>
    </li>
  </ul>
</article>

[refreshes so fast your hat flies off like in a cartoon]. WOW! Nothing... happened...? Correct! We get the exact same output to the browser, but our data is coming from Ruby!

Most of this is straightforward. For example, <%= @post_shark[:username] %> just means, "Grab the :username value from the @post_shark hash and display it in the context of this html."

Notice the comment display, however:

@post_shark[:comments][0][:username]

Here, @post_shark[:comments] returns an array of comments. (There's only one, right now.) To access the first one and return it, we call [0] (first, in code). So @post_shark[:comments][0] returns the only comment hash. Then, we grab the :username value from that.

Let's go through the same process with our other two posts:

<article class="post">
  <div class="user-info">
    <img src="<%= @post_whale[:avatar_url] %>" alt="<%= @post_whale[:username] %>">
    <h2><%= @post_whale[:username] %></h2>
    <h3><%= @post_whale[:humanized_time_ago] %></h3>
  </div>
  <a class="photo" href="#">
    <img src="<%= @post_whale[:photo_url] %>" alt="post from <%= @post_whale[:username] %>">
  </a>
  <div class="actions">
    <%= @post_whale[:like_count] %> likes
    <span class="comment-count"><%= @post_whale[:comment_count] %> comment</span>
  </div>
  <ul class="comments">
    <li>
      <p>
        <%= @post_whale[:comments][0][:username] %>: <%= @post_whale[:comments][0][:text] %>
      </p>
    </li>
  </ul>
</article>
<article class="post">
  <div class="user-info">
    <img src="<%= @post_marlin[:avatar_url] %>" alt="<%= @post_marlin[:username] %>">
    <h2><%= @post_marlin[:username] %></h2>
    <h3><%= @post_marlin[:humanized_time_ago] %></h3>
  </div>
  <a class="photo" href="#">
    <img src="<%= @post_marlin[:photo_url] %>" alt="post from <%= @post_marlin[:username] %>">
  </a>
  <div class="actions">
    <%= @post_marlin[:like_count] %> likes
    <span class="comment-count"><%= @post_marlin[:comment_count] %> comment</span>
  </div>
  <ul class="comments">
    <li>
      <p>
        <%= @post_marlin[:comments][0][:username] %>: <%= @post_marlin[:comments][0][:text] %>
      </p>
    </li>
  </ul>
</article>

Refresh to make sure everything's looking fresh (and... the same). Awesome.

looping

So this is an improvement for sure. But we should really make this even cleaner. All of these posts are behaving the same way in our html, so we don't need separate variables and separate html for each one.

DRY

This kind of repetition in code indicates an opportunity for you to employ the almighty programming principle: DRY (Don't Repeat Yourself). If you're copying/pasting code, chances are the computer can and should do that.

In our case currently, let's let the computer repeat the same code for each post. To do so, we're going to use a very common Ruby method: .each. Before we can, let's get a nice @posts array set up in our get '/' block in actions.rb:

get '/' do
  # post code

  @posts = [@post_shark, @post_whale, @post_marlin]

  erb(:index)
end

Great. Head to index.erb and BEHOLD:

<header>
  <h1>Finstagram</h1>
</header>
<main role="main">
  <% @posts.each do |post| %>
    <article class="post">
      <div class="user-info">
        <img src="<%= post[:avatar_url] %>" alt="<%= post[:username] %>">
        <h2><%= post[:username] %></h2>
        <h3><%= post[:humanized_time_ago] %></h3>
      </div>
      <a class="photo" href="#">
        <img src="<%= post[:photo_url] %>" alt="post from <%= post[:username] %>">
      </a>
      <div class="actions">
        <%= post[:like_count] %> likes
        <span class="comment-count"><%= post[:comment_count] %> comment</span>
      </div>
      <ul class="comments">
        <li>
          <p>
            <%= post[:comments][0][:username] %>: <%= post[:comments][0][:text] %>
          </p>
        </li>
      </ul>
    </article>
  <% end %>
</main>

Whoa. We've definitely DRY'd up some code here. That's a good thing. How did we do it?

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

That's the good stuff right there. We called .each on our @posts array which accepts a block with one block variable which we can name anything we want (in our case: post). Everything inside that block will be called (rendered since it's ERB) for each post in @posts. Isn't Ruby beautiful?

From there, it's simple, we just refer to each post by its block variable name (post) and grab those same hash values.

Now that we know about .each, let's allow our posts to have more than one comment, shall we?

<header>
  <h1>Finstagram</h1>
</header>
<main role="main">
  <% @posts.each do |post| %>
    <article class="post">
      <div class="user-info">
        <img src="<%= post[:avatar_url] %>" alt="<%= post[:username] %>">
        <h2><%= post[:username] %></h2>
        <h3><%= post[:humanized_time_ago] %></h3>
      </div>
      <a class="photo" href="#">
        <img src="<%= post[:photo_url] %>" alt="post from <%= post[:username] %>">
      </a>
      <div class="actions">
        <%= post[:like_count] %> likes
        <span class="comment-count"><%= post[:comment_count] %> comment</span>
      </div>
      <ul class="comments">
        <% post[:comments].each do |comment| %>
          <li>
            <p>
              <%= comment[:username] %>: <%= comment[:text] %>
            </p>
          </li>
        <% end %>
      </ul>
    </article>
  <% end %>
</main>

Very cool. By harnessing the power of .each and looping, we can save ourselves a ton of code and stay DRY.