This is the third part of the guide on how to build a simple Rails-inspired Ruby web application with Rack.

In part one we looked at Rack, what it does for us, and wrote the initial skeleton for the application; in part two we expanded on that and implemented a router and a controller system that mimics Rails’ own one. Now, in part three we are going to augment the controllers to use a templating system for the response bodies.

Disclaimer

Before we start, let me remind you once again that this tutorial does not attempt to build a production–ready app, and that the code examples are meant to be clear and simple, not performant or secure.
Getting this kind of app ready for production is an interesting exercise, and not an easy one (otherwise people would not be using frameworks), but it is not the purpose of these posts.

Simple templating

When we implemented the controllers in the previous post, we used Rails’ controllers as an example and replicated part of their interface. We finished with each controller’s action returning an HTML string inlined in the method implementation.

That solution made sense because we were focussing on designing the interface between application, router and controllers, but it’s naive and makes working with complex response bodies inpractical. We can do better and in fact we’re now going to replace those hardcoded strings with a templating system. This will require some small additions to the controllers, but we will not change how they work: they’ll still receive a Rack request object and return an Rack response array. In other words the templating system will be a private detail, and the rest of the application will interact with the controllers in exactly the same way.

Just like we modelled our controllers on Rails’ ones, we will build our templating system to mimic what is possible in Rails. Specifically, we are going to use the eRuby templatying system and make the controller’s instance @variables available to the ruby <% tags %> embedded in the templates.

On the subject of implementing the templating engine, the Ruby Standard Library includes an implementation of eRuby called ERB. Rails uses erubis rather than ERB, which provides better performance, but for this tutorial ERB will do just fine. For consistency with the file extensions, for the rest of this article I’ll always use “ERB” rather than “eRuby” to refer to the templating system.

On the subject of using the instance variables of a controller to pass data to the template, I must point out that that is not what Rails actually does. Still, that is the interface to the functionality and the mental model that Rails encourages. Our implementation will be different (much simpler) but it will provide the same apparent functionality.

Views and Templates

There are only two hard things in Computer Science, or I do not think it means what you think it means.

Rails uses the term views to refer to what are normally called templates.

A template is a reusable static text which contains placeholders meant to be substituted with values; the substitutions produce different texts with the same structure. Templates can be stored in template files.
The definitions of views vary. A view is the V in MVC, and has the responsibility to represent the Model data to the user. How this is accomplished varies greatly depending on the platform and the context, so that Objective-C views in Apple’s Cocoa framework are functionally different from the views of a web application framework. A view can be an object-oriented abstraction for a user interface component, or it can be a facade-like object that exposes the data in a simpler way and interpolates it into a template.

Thus, Rails’ use of the word view might confuse people who have already encountered the concept in other contexts. However, since so far we’ve tried to replicate the idioms of Rails, it’s probably a good idea to name our template directory views/. Hence we’ll add these new files to our app:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
rack_demo/
├─ app/
│  ├─ controllers/
│  │  ├─ base_controller.rb
│  │  └─ dogs_controller.rb
│  ├─ views/
│  │  ├─ base/
│  │  │  └─ index.html.erb
│  │  └─ dogs/
│  │     ├─ index.html.erb
│  │     ├─ new.html.erb
│  │     └─ show.html.rb
│  └─ router.rb
├─ application.rb
└─ rack_demo.ru

Notice how the templates are grouped in sub-directories that mirror the controllers.

We are also adopting the convention of using a chain of file extensions for the template files. Rails reads those extensions right-to-left and uses them to determine which templating engines the files should be processed with, and in which order, with the leftmost extension being the expected output format. Our templating system won’t be so sophisticated, but I find it a helpful visual clue for the files’ contents, so that it’s clear that foo.html.haml is a Haml file producing HTML, and foo.json.erb is an ERB file producing JSON.
In our app we will only support ERB, however, and although producing JSON rather than HTML would be as easy as adding the right template, we will not do it in the tutorial.

Rendering our first template

Let’s begin with something simple. The homepage HTML we added in the previous part is a good place to start from, because it’s not meant to display dynamic data. We can move the inlined HTML body into the file views/base/index.html.erb (and expand it a little):

1
2
3
4
5
6
7
8
9
10
11
12
13
<html>
  <head>
    <title>A Rack Demo</title>
  </head>
  <body>
    <h1>This is the root page</h1>
    <p>Hello from a template!</p>  
    <h2>Resources</h2>
    <ul>
      <li><a href="/dogs">Dogs</a></li>
    </ul>
  </body>
</html>

That looks simple enough. Now it’s time to update the controller to read the body from the file:

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
26
27
28
29
30
31
class BaseController
  # ...
  def index
    build_response render_template
  end

  private

  def render_template(name = params[:action])
    templates_dir = self.class.name.downcase.sub("controller", "")
    template_file = File.join(templates_dir, "#{name}.html.erb")

    file_path = template_file_path_for(template_file)

    if File.exists?(file_path)
      puts "Rendering template file #{template_file}"
      render_erb_file(file_path)
    else
      "ERROR: no available template file #{template_file}"
    end
  end

  def template_file_path_for(file_name)
    File.expand_path(File.join("../../views", file_name), __FILE__)
  end

  def render_erb_file(file_path)
    File.read(file_path)
  end
  # ...
end

Let’s look at what we just added.

The index action hasn’t changed much. Instead of passing an inlined String literal (well, a heredoc) to the build_response method, we are now passing it the return value of render_template, which is supposed to be a String as well.

The render_template method accepts an optional template name parameter, which defaults to the current action name. This gives us something similar to the automagic convention we have in Rails, where the template for a specific Controller#action pair is usually found in views/controller/action.html.erb and rendered automatically. In our simple app we have to use the method explicitly, but the mechanics are very similar. This also allows us to render templates from another action, again borrowing from Rails’ conventions: for example, the result of a failed create action could be rendering the template for the new action.

The first thing the method does is building a file name and obtain the file path with a call to template_file_path_for. Notice how on line 11 we’re enforcing a naming convention for the template: we’re explicitly looking for *.html.erb files. Even though we plan to support HTML and ERB only for the sake of simplicity, the naming convention is purely arbitrary.

If the file exists the render_erb_file method is used to read and return the contents of the file as a String. If the file is not found an error String is returned and it will be used as response body. (This is the kind of thing that is useful in development but should not happen in production)

The render_erb_file method for the moment is incomplete: it simply abstracts away from the technicalities of dealing with the file system. In the privous paragraph I described its purpose as to “read and return the contents of the file as a String”, but that should really evolve to “read the contents of the file, process them with an ERB rendering engine, and return the result as a String”.

At this point it should be easy to identify a couple of issues with the last snippet of code. We have a template file with extension .html.erb which only contains HTML without any ERB tags, and our rendering method is named render_erb_file even though it doesn’t handle ERB (yet). These two issues give us the opportunity to make a couple of important points.

The first issue might be confusing, but that file extension is technically correct. A text without ERB tags can be passed through an ERB engine and be returned unchanged, and the real requirement to qualify as an ERB template is that, if ERB tags are present, they must be syntactically correct (for example, you can’t have unclosed tags or Ruby syntax errors).

The second issue would be a problem, but it is fine at this stage because we’re moving a little step at a time. We coded the Application before the Router and we declared what kind of interface the Router had to have; later we wrote the Router before the controllers, and again we coded to an interface that we implemented only later. This is the same, and we’re doing it for a couple of reasons:

  1. We’re pushing the missing features down into the low-level private methods, which allows us to concentrate on the abstractions first.
  2. Writing it in that form mandates that the outside world should not care about how a template is rendered, as long as a string is returned.

With this digression on software-design out of the way, we can now proceed to improve the template rendering routine.

Dynamic templates

We have added the ability to render static template files and use them to generate HTTP response bodies. That’s good, but what we really need is the ability to render dynamic content. Let’s do it now.

We have not implemented any persistence mechanism yet (we will in the next post), and our router-controller stack is stateless, thus we can’t yet work with concepts like listing, viewing and creating resources. If we want to display dynamic content, we’ll have to either use something else or cheat. For example, we can set some action–specific values to use in the templates, and we can extract and present data from the request parameters.

Let’s begin by adding the template files.

In views/dogs/index.html.erb we will have:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<html>
  <head>
    <title><%= @title %></title>
  </head>
  <body>
    <div>
      <h1><%= @title %></h1>
      <p><a href="/dogs/new">Create a new dog</a></p>
      <ul>
        <% @dogs.each do |dog| %>
          <li>
            <a href="/dogs/<%= dog.id %>">
              <%= "#{dog.id} - #{dog.name}" %>
            </a>
          </li>
        <% end %>
      </ul>
    </div>
  </body>
</html>

In views/dogs/new.html.erb:

1
2
3
4
5
6
7
8
9
10
11
12
13
<html>
  <head>
    <title><%= @title %></title>
  </head>
  <body>
    <div>
      <h1><%= @title %></h1>
      <form action="/dogs" method="POST">
        <!-- we'll worry about the form in the next post -->
      </form>
    </div>
  </body>
</html>

And in views/dogs/show.html.erb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
  <head>
    <title><%= @title %></title>
  </head>
  <body>
    <div>
      <h1><%= @title %></h1>
      <p><a href="/dogs">Back to the list</a></p>
      <p>
        Dog #n <%= @dog.id %>, name: <strong><%= @dog.name %></strong>
      </p>
    </div>
  </body>
</html>

As you can see, they contain references to Ruby instance variables that will come from the controller object. Instead of persisted records we can use the very flexible OpenStruct class from the standard library to build the dog objects on the fly.

Before updating the DogsController, however, let’s have another look at the dog_page method we added at the end of the previous post. Even though that was a heredoc and not an external template file, it was a quick and dirty dinamic template renderer. Well, we don’t need it anymore.

The new controller 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
26
27
28
29
30
31
32
33
34
35
36
require_relative './base_controller.rb'
require 'ostruct'

class DogsController < BaseController
  # GET /dogs
  #
  def index
    @title = "So many dogs"
    @dogs = (1..5).map do |i|
      OpenStruct.new(id: i, name: "Dog-#{i}")
    end
    build_response render_template
  end

  # GET /dogs/:id?name=Optional%20Custom%20Name
  #
  def show
    dog_name = params["name"] || "Dog-#{params[:id]}"
    @title = "#{dog_name}'s page"
    @dog = OpenStruct.new(id: params[:id], name: dog_name)
    build_response render_template
  end

  # GET /dogs/new
  #
  def new
    @title = "More dogs please"
    build_response render_template
  end

  # POST /dogs
  #
  def create
    redirect_to "dogs/"
  end
end

Let’s look at how it works.

All actions but create set a @title, which will be used in the templates.

In index we build a list of dog objects in a loop. Using OpenStruct ensures that their attributes can be dynamically set on initialization and then accessed with dot notation. Their id values will be used in the links leading to the show page. These dog objects are admittedly not very interesting, and are here just as a proof of concept.

In show we build a single dog object, and we set its id to whatever we find in the request parameters. That’s because, in order to be RESTful, the application must show a resource identified by the requested id. We’re not doing anything to validate the value of id, yet, thus we are not limited to the paths provided in the index page: /dogs/1 will work, but so will /dogs/ninjacat or anything other than /dogs/new (because it will be routed to another action). The names of the dogs are derived from the id, but we also support an optional name parameter that can be passed in the query string, so that /dogs/2 and /dogs/2?name=Wooffer will result in different pages.

The new action only sets the title, and create is still not implemented.

With all this new code in place, we can try to access any of the dog pages. They won’t work yet, and in fact all the ERB tags and embedded ruby code is rendered as it is in the HTML text.
That’s because first we need to update BaseController#render_erb_file to be:

1
2
3
4
5
6
7
8
9
10
11
require 'erb'

class BaseController
  # ...
  private

  def render_erb_file(file_path)
    raw = File.read(file_path)
    ERB.new(raw).result(binding)
  end
end

Let’s look at what’s changed.

On line 1 we require the erb library, and the raw on line 8 contains the unprocessed file contents that we’ve just seen.
On line 9 we process the template and return the produced String. We initialize an instance of ERB with the unprocessed template text, and we obtain the result by providing the current Binding object as evaluation context. Passing the current Binding will allow ERB to access the controller’s instance variables. The binding method will return the Binding of the current invocation context, so that it will work correctly in subclasses (e.g. DogsController) even if the method is defined in the parent BaseController.
All of this violates encapsulation and separation of concerns, but it’s a fast and simple way to mimic the interface provided by Rails.

With this change, our dynamic templates will finally work as expected.

Partial templates

So far so good, but there is another feature we can add before calling it a day. At the moment, our template files must be complete and self-contained HTML documents, whereas Rails has gotten us used to working with layouts and partials.
Considering that we are building a very simple application without any styling, there is not a lot of benefit in implementing layouts. Partials, on the other hand, would allow us to extract commonly used components like the page title. Let’s add support for partial templates, then.

Again, and purely because of the nice visual cue, we adopt the Rails convention to prefix partial file names with an underscore. Let’s add a partial for the HTML <head> in the views/base/ directory, because we plan to use it in all templates:

1
2
3
4
5
6
rack_demo/
└─ app/
   └─ views/
      └─ base/
         ├─ _head.html.erb
         └─ index.html.erb

The partial file should have this content:

1
2
3
<head>
  <title><%= @title || "Rack Demo" %></title>
</head>

To use it in the other templates, we can replace all our <head> blocks with this:

1
<%= render_partial "base/_head.html.erb" %>

and of course we have to implement the render_partial method in a lexical context where it will be available to the ERB engine. What does it mean?
When the ERB engine encounters an embedded Ruby statement that can be executed locally, for example <%= "foo: #{1 + 1}".reverse %>, it will do just that and move on. When the embedded statement references variables, for example <% @foo + bar %>, it will try to find the variables in the local evaluation context. The variables could be locally scoped, or could exist in the context encapsulated in the provided Binding. The same is true for method invocations.

Thus, we can implement render_partial along the other template rendering methods in BaseController:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class BaseController
  # ...
  private

  def render_partial(template_file)
    file_path = template_file_path_for(template_file)

    if File.exists?(file_path)
      puts " > Rendering partial file #{template_file}"
      render_erb_file(file_path)
    else
      "ERROR: no available partial file #{template_file}"
    end
  end
  # ...
end

That is very similar to the render_template method we already have, and the concept is basically the same. With some refactoring we could even use a single method in both situations, but I prefer to keep them distinct because they have different responsibilities and, as the complexity of the application increases, their requirements will evolve in different directions.

Try now to reload any of the pages, and everything should still work.

Wrap up

This is the end of the third part. We now have a modularized Rack application with a Router, resource-oriented controllers and a dynamic template systems. Adding new paths and resources is a matter of adding new controller classes and implement the right methods. Even though this whole endeavour is a simple proof of concept, things are shaping up quite nicely.

The next and final step will be to add a persistence layer and model objects to the application, which I’ll write about in the next post.