LaunchSchool - An Online School for Developers /

Blog

Growing Your Own Web Framework With Rack Part 3

Part 2 of this series focused on routing and expanding our application to serve back HTML responses. In this post, we’ll start to think about separation of responsibilities between our core routing logic and our views. We’ll introduce a library ERB that will help us turn Ruby code into HTML. Finally, we’ll update our application code to include view templates.

View Templates

We need a location in our application where we can store and maintain code related to what we want to display. This type of code is called a view template. View templates are separate files that allows us to do some pre-processing on the server side in a programming language (Ruby, Python, JavaScript, etc) and then translate programming code into a string to return to the client (usually HTML).

Right now in our code, we’re including HTML directly inside the string that we’re returning as the response. We’re including dynamic content in that string by using string interpolation. But if we want to take advantage of the separation of concern that view templates gives us, how do we incorporate them into our application? Before we answer that question, we’ll take a quick detour and study a popular Ruby templating library.

ERB

There are many view templating libraries and options, but in this application we’ll use a templating engine called Embedded Ruby or ERB. ERB allows us to embed Ruby directly into HTML — hence “Embedded” Ruby. The ERB library can process a special syntax that mixes Ruby into HTML, and produces a final 100% HTML string. That’s all it does.

In our application, we’ll be using ERB in conjunction with separate view template files — .erb files. But before we work with files, let’s try out the ERB templating engine in IRB.

To use ERB, first we must:

  • require 'erb'
  • Create an ERB template object and pass in a string using the special syntax that mixes Ruby with HTML.
  • Invoke the ERB instance method result, which will give us a 100% HTML string.

The end result is that we can use ERB to convert a string with Ruby mixed with HTML into a string that is 100% HTML.

Let’s hop into IRB and try all of this out:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
irb(main):001:0> require 'erb'
=> true
irb(main):002:0> def random_number
irb(main):003:1> (0..9).to_a.sample
irb(main):004:1> end
=> :random_number
irb(main):005:0> content1 = ERB.new("<html><body><p>The number is: <%= random_number %>!</p></body></html>")
=> #<ERB:0x007fa2d211d470 ... >

irb(main):006:0> content1.result
=> "<html><body><p>The number is: 7!</p></body></html>"

irb(main):007:0> content2 = ERB.new("<html><body><p>The number is: <%= random_number %>!</p></body></html>")
=> #<ERB:0x007fa2d298e410 ... >

irb(main):008:0> content2.result
=> "<html><body><p>The number is: 8!</p></body></html>"

Note that we’re instantiating a new ERB template object with Ruby and HTML mixed together. Note also that we’re using the <%= %> special syntax to let ERB know how to process this mixed content.

We’re using the method random_number to get some dynamic content, and then invoking that method by using the ERB syntax <%= %>. The final output from ERB is pure HTML.

The <%= %> are ERB tags that are used to execute Ruby code that is embedded within a string. There are two varieties: one that doesn’t have an equal sign, and one that does.

  • <%= %> – will evaluate the embedded Ruby code, and include its return value in the HTML output. A method invocation, for example, would be a good candidate for this tag.
  • <% %> – will only evaluate the Ruby code, but not include the return value in the HTML output. You’d use this tag for evaluating Ruby but don’t want to include its return value in the final string. A method definition, for example, would be a good use case for this tag.

ERB can also be used to process entire files, and not just strings. Just like the strings that contained a mix of Ruby and HTML, ERB files also follow the same format. ERB files need to end with an .erb extension and use the same special tags to embed Ruby. Let’s say we had a file called example.erb that looked like the below.

1
2
3
4
5
6
7
<% names = ['bob', 'joe', 'kim', 'jill'] %>

<html>
  <body>
    <h4>Hello, my name is <%= names.sample %></h4>
  </body>
</html>

If we had an .rb file in the same directory as the example.erb file above, we could process that erb file like this:

1
2
3
4
5
require 'erb'

template_file = File.read('example.erb')
erb = ERB.new(template_file)
erb.result

The result would be a 100% HTML string that looks something like <html><body><h4>Hello, my name is jill</h4></body></html>. Of course, the name would change every time since it is dynamic content. Note: if you’re following along, you might see some extra newline characters (\n) in the final string; that’s ok, and can be ignored for now.

That’s a quick overview of how to work with ERB. Now let’s take a look at how to use ERB with files to make our Rack web application more flexible and accommodating using view templates.

Adding in View Templates

To start off, we’ll create a view template for the information we wish to display to the user. This will be the default view template for our application. Let’s call it, index.erb. We also have to include the same message we’ve been using for our root route: “Hello World!”.

views/index.erb

1
2
3
4
5
<html>
  <body>
    <h2>Hello World!</h2>
  </body>
</html>

We want some organization within our application, so for now, we’ll put all view templates in the views folder. This folder should be located at the top-level of our application, along with config.ru and hello_world.rb.

Our application should now look like this:

1
2
3
4
5
6
7
8
my_framework/
 ├── Gemfile
 ├── Gemfile.lock
 ├── config.ru
 ├── hello_world.rb
 ├── advice.rb
 └── views/
       └── index.erb  

Now that we have our index.erb view template, we need to read it into our application and use it. We can treat it just like any other file and use the .read method from the Ruby File class.

And after we have our view template in string format, we can pass it into an ERB object and use that as a way to get our response body. Lines 7-9 below show this in code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# hello_world.rb

class HelloWorld
  def call(env)
    case env['REQUEST_PATH']
    when '/'
      template = File.read("views/index.erb")
      content = ERB.new(template)
      ['200', {"Content-Type" => "text/html"}, [content.result]]
    when '/advice'
      piece_of_advice = Advice.new.generate
      [
        '200',
        {"Content-Type" => 'text/html'},
        ["<html><body><b><em>#{piece_of_advice}</em></b></body></html>"]
      ]
    else
      [
        '404',
        {"Content-Type" => 'text/html', "Content-Length" => '48'},
        ["<html><body><h4>404 Not Found</h4></body></html>"]
      ]
    end
  end
end

Just as we did earlier from IRB, we take a string value (the contents of index.erb) and pass that in ERB.new. Then, we can use the #result method to get a 100% HTML string output for the response body. The difference this time is that we’re getting that string value from a file, the view template.

We’ve set up all that we need to use a view template as our response body. Now, let’s run our application and see if we get the correct response (make sure to first restart the server):

1
2
3
4
5
6
7
8
9
10
11
12
13
$ curl -X GET localhost:9595 -m 30 -v
HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Type: text/html
Date: Tue, 29 Nov 2016 02:38:50 GMT
Server: WEBrick/1.3.1 (Ruby/2.3.3/2016-11-21)
Transfer-Encoding: chunked

<html>
  <body>
    <h2> Hello World!</h2>
  </body>
</html>

For this particular page, we don’t actually need ERB. There isn’t any dynamic content related to our index page. Yet, we’ll need it for the routes that require dynamic data, so to keep everything consistent, we’ll be using .erb files for all our other routes. This also leaves options open for us; if we want to add dynamic content to index.erb later, then we can easily do so.

If you’d like to see what it looks like with a browser, you can navigate your browser to localhost:9595 and you should see the following (notice the text is now a h2 header).

Conclusion

In this post, we’ve set up our application to use a view template. Now, our main application file used for routing doesn’t get bogged down as much by view related code in the root route. Yet, take a look at our other two routes. Our application code could get very messy, depending on the size of the response we want to potentially return. Separating our view related code to their own files really helps in keeping our code base more manageable. In part 4 of this series, we’ll continue to build up our application and then start extracting common code in order to plant the seeds for our very own web development framework.

rack, ruby