Templates

In this chapter you will learn how to create a web server with Ruby, how to use templates and a bit of programming to avoid repeating yourself. You will also get a taste of creating dynamic content, i.e. the page content is not always the same when somebody visits it.

Pictures page

Last time we created a navigation menu with a link to the pictures.html page, but that page doesn’t yet exist. Let’s create it now.

Make a copy of about.html, name it pictures.html, and change its title and heading. Find about a dozen free images from Public Domain Pictures or some similar site. Add the images to your pictures.html page using the <img> tag.

<img class="album-photo" src="http://www.publicdomainpictures.net/pictures/50000/t2/cat-looking-up.jpg">
<img class="album-photo" src="http://www.publicdomainpictures.net/pictures/30000/t2/cat-in-the-city-5.jpg">
<img class="album-photo" src="http://www.publicdomainpictures.net/pictures/30000/t2/annoyed-cat.jpg">
<img class="album-photo" src="http://www.publicdomainpictures.net/pictures/30000/t2/cat-in-the-city-2.jpg">
<img class="album-photo" src="http://www.publicdomainpictures.net/pictures/50000/t2/cat-in-chair.jpg">
<img class="album-photo" src="http://www.publicdomainpictures.net/pictures/30000/t2/cat-hunting.jpg">

Use CSS to make the images look like a photo album. In case the images are of different size, you can set their height; the web browser will adjust their width automatically to maintain the image’s aspect ratio.

The following example uses the CSS properties border, background-color, margin, padding and box-shadow.

Pictures page with some pictures

Don’t Repeat Yourself

In programming we have a principle called Don’t Repeat Yourself (DRY) which says that “every piece of knowledge must have a single, unambiguous, authoritative representation within a system.” If we follow that principle, our code will be easier to understand and modify.

Our little web site is already breaking the DRY principle. Both of our pages are repeating the HTML for the layout and navigation. If we would add another page, we would have to change the navigation menu in many different HTML files. Additionally the pictures page has lots of similar <img> tags with just the URL changing. To solve this problem, we will introduce templates, so that all HTML code will be written once and only once.

Run a web server

This far we have created our site with just static HTML pages, but in order to use templates we must create a web server which will dynamically generate HTML pages. Your web browser will communicate with the web server and show the HTML pages which the web server generates.

We will use the Ruby programming language and the Sinatra web framework to create the web server. Follow the installation guide to install Ruby and Sinatra if you haven’t yet done so.

Create a file called app.rb and put the following code in it. This minimal web application contains a single route for the path /hi which will just generate a page with the text Hello World!

require 'sinatra'
require 'sinatra/reloader'

get '/hi' do
  "Hello World!"
end

Then run the command ruby app.rb in your terminal. It should start a web server on your own machine in port 4567. To view it, visit http://localhost:4567/hi in your web browser. It should show the Hello World! text.

Your first web server is working

When programming, it is often necessary to stop and restart the web server to see your changes in action.

To stop the web server by pressing Ctrl+C in your terminal. Try refreshing the page in your web browser. It should now show an error message because there is no more a web server to connect to.

To start the server again, without having to retype the command, press Up to see your previous command and then press Enter.

Static site on a web server

As a first step towards a dynamic web site, we can use Sinatra’s ability to serve static files to serve our existing HTML and CSS files unmodified.

Inside the same folder as app.rb, create a new folder called public and move your HTML and CSS files and pictures there. Then start your web server with ruby app.rb, visit http://localhost:4567/pictures.html in your web browser. Your site should look the same as before, but this time it’s being served to you by a web server the same way as all web sites on the Internet.

Note: If the full path of your project folder contains non-English letters, such as åäö, Sinatra will fail to serve your static files. In that case move your project folder for example to C:\project

Your web site on a web server

Fix the front page

You should make sure that people can visit your web site through its base URL, the same way as all web sites on the Internet, instead of having to know one of its sub pages.

First try going to http://localhost:4567/ on your current site. It should show an error message “Sinatra doesn’t know this ditty.” Let’s fix that.

Change app.rb to redirect the path / to /about.html by adding the following route.

get '/' do
  redirect to('/about.html')
end

Check that when you visit http://localhost:4567/, your web browser immediately goes to the http://localhost:4567/about.html address.

Templates

The idea of templates is to sprinkle your HTML code with some placeholder marks where you can easily insert some dynamic content. We will use the ERB templating library for that.

Create a views folder inside your project folder (next to the public folder). This is the folder where Sinatra expects to find all your template files.

Create a layout.erb file inside the views folder. Copy from public/about.html to views/layout.erb all code except the page content (i.e. the stuff between <section class="content"> and </section>). Replace the page content with just <%= yield %>

Copy public/about.html to views/about.erb and remove everything except the page content (i.e. the opposite of what you did in views/layout.erb). Copy public/pictures.html to views/pictures.erb and do the same thing. Remove the old public/about.html and public/pictures.html.

Add the following routes to app.rb to render the above mentioned ERB templates.

get '/about.html' do
  erb :about
end

get '/pictures.html' do
  erb :pictures
end

Check that all the pages still look the same as before in your web browser. The difference is that now all the common HTML for the layout and navigation is in a single place, so changing it and adding new pages will be easier. Templates also make it possible to have dynamic content, as we will see next.

Lottery

You might have seen on the Internet many sites which say “Click here to find out your lucky number”. Here is how you can make your own using a couple of lines of code and templates.

In app.rb, change the /about.html route to contain the following code.

get '/about.html' do
  backstreet_boys = ["A.J.", "Howie", "Nick", "Kevin", "Brian"]
  @who_i_marry = backstreet_boys.sample
  erb :about
end

The above code first creates a list of the names of all Backstreet Boys and stores it in the backstreet_boys variable. The next lines calls the sample method on that list, which will randomly pick one of the items in the list, and then the code stores it in the @who_i_marry variable. Because the variable name starts with @, also the template can access that variable.

In views/about.erb, add the following code to show the value of @who_i_marry on the page.

<p>I like Backstreet Boys a lot. I'm going to marry <%= @who_i_marry %>!</p>

Try reloading the about page in your web browser multiple times. The name should change randomly every time that the page is reloaded.

Lottery result is shown

Front page at the root

Normally the front page of every web site is at the root path (/), but on our web site it’s instead at the /about.html path. We can solve that by rendering the about template at the root path instead of doing a redirect.

In app.rb, remove the old / route and change the /about.html route to be the new / route.

In views/layout.erb, change the link to the about page from <a href="about.html"> to <a href="/">.

Check that when you visit http://localhost:4567/, your web browser stays at the http://localhost:4567/ address and shows the front page.

Front page at the root

Album photo template

In addition to inserting single items to a template, as we did a moment ago, we can insert a list of items and repeat a piece of the template for each of them. To demonstrate that we will remove the repetition of multiple image tags on the pictures page. Using templates might seem overkill for simple image tags, but imagine that each image will also have a description, like button and a link to the full-sized picture, in which case avoiding the repetition becomes very important.

Change your /pictures.html route to store a list of the picture URLs in a variable.

get '/pictures.html' do
  @picture_urls = [
    "http://www.publicdomainpictures.net/pictures/50000/t2/cat-looking-up.jpg",
    "http://www.publicdomainpictures.net/pictures/30000/t2/cat-in-the-city-5.jpg",
    "http://www.publicdomainpictures.net/pictures/30000/t2/annoyed-cat.jpg",
  ]
  erb :pictures
end

In views/pictures.erb, use a for loop to repeat the image tag for each url in the list.

<% for url in @picture_urls %>
<img class="album-photo" src="<%= url %>">
<% end %>

Now check that the pictures page still looks the same as before. Also try adding a couple more pictures to make sure that the template works how it should.

Show all pictures from a folder

There is still some work involved in adding the picture URLs by hand to the list in the route. We can simplify that by making our application generate the list automatically based on what pictures there are in a folder.

Create a pictures folder under the public folder and save there all the pictures in your list.

Change your /pictures.html route to generate a list of the picture URLs based on the contents of the public/pictures folder.

get '/pictures.html' do
  @picture_urls = Dir.glob('public/pictures/**').map { |path| path.sub('public', '') }
  erb :pictures
end

Check that the pictures page still works. Try adding a couple more pictures - much easier now, isn’t it?

How does this code work?

The above code combines multiple operations to achieve the desired result. First glob finds all files under the public/pictures folder and returns a list of their paths. Each of those paths is like public/pictures/something.jpg, but on the pictures page we need them to be like /pictures/something.jpg. That’s why we use map to go through each path in the list and remove the public prefix from the paths using sub.

For the purposes of this tutorial, it’s not necessary to fully understand how the code works. But to get better at programming, one question to ask is that do I really understand every word and symbol in the code? One way to learn more is to read the documentation. Another way is to debug your code by printing things to the terminal when you run your application. For some programming languages, including Ruby, there are also tools such as a debugger and a REPL.

Rant over. The tutorial must go on. No time for deep learning yet. ;-)

Unique titles for every page

Earlier when we started using templates, our web site didn’t anymore have unique titles for every page. Now that we know how to use variables in templates, we can give each page its own title.

In views/layout.erb, get the page title from the @title variable.

<title><%= @title %></title>

Add to each of your routes the code for setting the title.

@title = "type the title here"

Visit every page on your site to make sure that they still work and that they have different page titles.

Unique page titles

Pages for individual pictures

It would be nice to be able to click a picture on the pictures page to see that picture in full size and also allow people to write comments for that picture. For that we will need to have separate pages for every picture. Because our application can have an unlimited number of pictures, it’s not possible to create separate routes for every picture; we need one route to handle all the pictures.

In views/pictures.erb, make each of the pictures a link to another page. The target of the link will be the URL of the picture, but with the file extension changed to .html. The following code does it using regular expressions.

<% for url in @picture_urls %>
<a href="<%= url.sub(/\.\w+$/, '.html') %>"><img class="album-photo" src="<%= url %>"></a>
<% end %>

In app.rb, add the following route which uses named parameters. The :picture will match anything in that position of the path and will make it accessible as params['picture'].

get '/pictures/:picture.html' do
  "Here will be a page for " + params['picture']
end

Go to your pictures page and click some of the pictures there. Notice how the text shown on the page is related to page’s address.

Placeholder page for a picture

Picture page template

Create views/picture.erb with the following content.

<img class="full-photo" src="<%= @picture_url %>">

<p><a href="pictures.html">Return to album</a></p>

Make the route render that template.

get '/pictures/:picture.html' do
  @title = "Picture"
  @picture_url = params['picture'] + '.jpg'
  erb :picture
end

Visit a few picture pages pictures and check that they show the picture. You’ll notice that the CSS and some links don’t work, so that needs to be solved next.

Broken page for a picture

On a picture page, if you click the “Pictures” or “Return to album” link, it will take your browser to http://localhost:4567/pictures/pictures.html instead of http://localhost:4567/pictures.html. Similarly it tries to load your CSS from http://localhost:4567/pictures/style.css instead of http://localhost:4567/style.css (you can see this with your web browser’s developer tools). However, the link to the front page still works.

The reason has to do with absolute vs. relative paths. Absolute paths start with http:// or similar. Other paths are relative to the page where the link appears. To make links point to the same target regardless of the page where it appears in your site, the link’s path should start with / so that it will be relative to the site’s root path instead of the current page’s path.

Change all relative links in views/layout.erb and views/picture.erb to start with the / character. Check that the picture pages look right and all the links work.

Working page for a picture

Don’t guess the picture’s file extension

Not all pictures have the .jpg file extension. There are .png, .gif and other image types as well. But currently the /pictures/:picture.html route has been hard-coded to work with only .jpg images.

A solution for that is to check the contents of the public/pictures folder to find out the actual file extension. This can be implemented by taking advantage of the code that already lists all pictures in the folder, so we will extract that code into a new method for reuse.

In app.rb, add the methods picture_urls and find_picture_url as shown below, and change your routes to use them to set @picture_urls and @picture_url.

get '/pictures.html' do
  @title = "Lovely Pictures"
  @picture_urls = picture_urls
  erb :pictures
end

get '/pictures/:picture.html' do
  @title = "Picture"
  @picture_url = find_picture_url(params['picture'])
  erb :picture
end

def picture_urls
  Dir.glob('public/pictures/**').map { |path| path.sub('public', '') }
end

def find_picture_url(basename)
  picture_urls.select { |path| File.basename(path, '.*') == basename }.first
end

Error page when the picture is not found

Currently if you go to a picture page that doesn’t exist, for example http://localhost:4567/pictures/foo.html, it shows a page with the picture not working. But instead of that a well behaving site should give an error message that the page was not found.

In HTTP there are a bunch of numeric status codes, of which the code 404 means that a page was not found. In Sinatra we can produce that status code with halt 404. Additionally we can create a not_found route to produce an error message page (otherwise the page is empty). It could produce a full web page, but a simple “Page Not Found” text is enough.

Update your /pictures/:picture.html route to be as follows.

get '/pictures/:picture.html' do
  @title = "Picture"
  @picture_url = find_picture_url(params['picture']) or halt 404
  erb :picture
end

not_found do
  "Page Not Found"
end

Check that http://localhost:4567/pictures/foo.html shows an error message. Also use the developer tools to check that the status code for that page is 404.

Page not found error shown with developer tools

Proceed to the next chapter