Software developer blog

Web app crash course (Part 1)

This is a beginner tutorial that shows you how to build a Ruby app using a certain set of technologies that I prefer to use. It will not make you a web developer right away, but it will show you the basics, and serve as a starting point for further reading. I assume that you already have basic knowledge ruby, HTML and CSS, or at least have read a tutorial that introduced you to them. If not, visit Codecademy. Another assumption I make, is that you are using Ubuntu (or possibly another Debian derivative) as an operating system. If not, you can download it free, and install it next to your existing operating system.

The approach I have taken is to show you most of the tools at once at a very low level, so that first you get the big picture, and then we will drill deeper later. This may be overwhelming at first, but bare with me, slowly everything will unfold.

By the end of the first part we will have a web page, that can say hello. Not much, but it's not the the goal we reach that will be of importance, but the way we get there. I will show you the tools developers use from day to day. We will look at how we manage gems, the building blocks of software development. Then we will look at some of those gems, most importantly Sinatra, that is designed to respond with web pages for requests of web browsers. We will also take a quick look at git, a tool, that is designed to store source code, and keep several versions of it around, so we can go back, when something goes wrong. Finally we will look, at how you can make your page look better with a Mustache.

The Gemfile

In the early days of software development computers came with a very basic set of instructions, and a language that was designed to invoke those instructions. To build something useful from just those instructions required enormous efforts from programmers, and it was also soon apparent that many of the tasks had to be repeated. So people came up with ways to group their programs into useful chunks that could be reused in other programs making it lot easier to build new programs on top of those later. Language designers realized, that they can create many of these little batches of code - so called libraries - themselves, and sell it along with the language. In case of ruby a different choice was made: these libraries are not installed by default, but they are available as so called gems, and most of them are written and made available by the community of ruby programmers. If we wish to use them, they need to be installed.

I will assume, that you have already installed ruby using rvm. (If not, visit rvm.io, and install rvm with ruby 2.1.1.) Once that's done we will start by creating a file with the filename Gemfile that will list all of the gems that we depend upon. Note that this file has no extension, it's just called Gemfile. The purpose here is to make sure that when anyone copies our code, they can use it almost instantly, since bundler will install everything they need. This is our initial Gemfile:

Gemfile
source 'https://rubygems.org'
 
gem 'sinatra'

The first line tells bundler to look for gems on rubygems.org, a public gem repository. The last line indicates that we will use sinatra.

Once the Gemfile is saved open a terminal, go to the project directory and execute: bundle. (If you don't have bundler installed, install it with gem install bundler) This will install sinatra, and it will create a file called Gemfile.lock that contains the exact versions of each gem used. We will commit both of these files to git.

Create a git repository

Version control systems are designed to store our code, share it with others, and keep track of the changes, so that we can go back when something goes wrong. Git is currently the most popular version control system, so we will use that. While we are developing our app, I will show you some of the basics of git. For now we will just learn how to create a git repository, and how to save our changes.

If you don't have git installed on your machine, please do so by issuing the commands bellow. Remember to replace "Your name" and "you@example.com" with your actual name and e-mail address.

sudo apt-get install git
git config --global user.name "Your Name"
git config --global user.email you@example.com

Now all you need to do is type git init in your project directory and press enter. This will turn your directory into a git repository. Later we will learn how to copy your repo to github, but for now it's enough for us to have a local copy.

Now if you run git status you will receive something like following message:

# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#	.idea/
#	Gemfile
#	Gemfile.lock
nothing added to commit but untracked files present (use "git add" to track)

The .idea directory is only present because I use RubyMine for editing the files. Your editor may also leave behind files. We want to avoid committing these files to the repository. To make sure that git won't complain about them let's create a file called .gitignore and add the list of files we wish to ignore. (One per line.) In my case this is just the .idea directory.

First we need to tell git which files should be under version control. To add our files type this command into your terminal: git add .gitignore Gemfile Gemfile.lock

Finally we will commit our changes to git: git commit -m 'Gemfile and .gitignore'

The -m option is used to specify the commit message which follows after it between single quotes. This message will later show up in our commit history, and will help us in determining what we did in each commit.

Say hello to the world

Up until now we were just preparing, now we will create our very first Sinatra app. It won't do much: just says hello to the world. Let's create a file called app.rb that will contain the code below:

app.rb
require 'sinatra'
 
get '/' do
  'Hello world!'
end

The first line will import Sinatra. After that we specify a get path - I will explain what that means later - and return the string 'Hello world!' whenever that path is opened in a browser. For now it's enough to know that the string between get and do is the path that comes after the domain name when we open the page in a browser.

Let's take this for a spin. Type this into your terminal: bundle exec ruby app.rb. Sinatra will start, and your terminal will hang. Now open up your favorite browser and open this web address: http://localhost:4567/. You should see a simple page with a hello world message on it.

Once you are done enjoying this wonderfully designed page, go back to your terminal and press CTRL + C to stop Sinatra.

What happened?

First we started a web server implemented in ruby. We use bundler to manage our gem dependencies, that is why we need to run our application with bundle exec in front of it. With ruby app.rb we run the script we have just written. When we do that Sinatra will start up an HTTP daemon that can receive and process HTTP requests. At this point Sinatra is running on your machine and is listening to requests on port 4567. If we used the default HTTP port 80, then it would be enough to open localhost in the browser. (localhost is a domain name that usually points to the machine you are working on.) Since only the root user can start a deamon on port 80 we used a different port, and that had to be specified in the url.

When we open localhost:4567 in the browser Sinatra receives a request containing the path /. It looks through the path that we have set up in our script (currently only '/') and executes the block that is specified for that path. Once the block returns, the string returned is sent to the browser, and it displays it.

Next I'd like to set up a more interesting path in our app.rb, but before I do that, let's commit to git: git add app.rb and after that git commit -m 'Hello world app'.

If you haven't followed along so far, or you are not sure you did everything right, you can download what we have so far from my GitHub account: Hello world app.

Say more

As a next step we will look at some ways to set up path in Sinatra. The examples below will be deleted when we start to implement our real app, but are beneficial to learn the basics of Sinatra. Our first example is a page available on http://localhost:4567/goodbye that will simply show the string "Goodbye!":

app.rb
get '/goodbye' do
  'Goodbye'
end

Sinatra wouldn't be very useful, if we had to specify every single url on our server one-by-one. We may wish to generate a page using part of the url. Our next example will return "Hello Joe!" when the page opened is http://localhost/hello/Joe, and "Hello Bob!" if the page we opened is http://localhost/hello/Bob:

app.rb
get '/hello/:name' do
  "Hello #{params[:name]}!"
end

As you can see, we can parse parts of a url by specifying a key - in this case :name - and the string that is matched there will appear in the params hash for that key.

At this point I'll commit to git again. By default git does not commit files that have changed, we first need to add them to the so called stage. Since we have changed the app.rb we need to add it again. However most of the time we just wish to commit all files that changed. In that case we can use the -a option of git, that will add every file that changed to our commit: git commit -a -m 'More Sinatra examples'. Note that -a will not add new files to the stage, only those that have already been under version controll and changed. Download here.

Mustache templates

A single line of text is rarely useful as a web page. Usually we would like to return an entire HTML page, that contains dynamically generated data. For this purpose we use templates. A template is a file containing HTML code and some placeholders for values that we will inject into the template. There are several template languages, my favorite is mustache. Partly because I'm a hipster, but most importantly because it is very minimalist. Other template languages have control structures, and can be misused to a level where the application logic leaks into the template. Mustache does not let you do that, which is actually good.

Let's see a simple mustache template. Create a file called hello_view.mustache, copy this template into it:

hello_view.mustache
<!DOCTYPE html>
<html>
    <head>
        <title>Hello {{{user_name}}}!</title>
    </head>
    <body>
        <h1>Hello <i>{{{user_name}}}</i>!</h1>
    </body>
</html>

As you can see, this is just a generic HTML file, except for the {{{user_name}}} part. A placeholder is a name between 3 pairs of curly braces.

To use the mustache template we first need to add the musthace gem to our Gemfile:

Gemfile
source 'https://rubygems.org'
 
gem 'sinatra'
gem 'mustache'

And we need to run bundle again, to install mustache.

Now we will create a view for this template. A view is a class that inherits from Mustache and contains all data that is necessary to render a template. In our case, the HelloView - defined in a new file called hello_view.rb - will have a single function: user_name.

hello_view.rb
require 'mustache'
 
class HelloView < Mustache
  def user_name
    "World"
  end
end

We also update app.rb:

app.rb
require 'sinatra'
 
require_relative 'hello_view' # < this line was added
 
get '/' do
  HelloView.new.render  # < this line changed
end
 
get '/goodbye' do
  'Goodbye'
end
 
get '/hello/:name' do
  "Hello #{params[:name]}!"
end

We had to require the HelloView. We also replaced the simple string we used before with a new instance of HelloView and called render on it immediately. Restart Sinatra, and open localhost:4567 in your browser again, and it will look nicer.

I will commit this again! First I add the new files: git add hello_view.* and then I commit git commit -a -m 'First mustache template example'

Now let's do the same for our '/hello/:name' path. For that we first need to make sure that we can change the result of the user_name function in the HelloView from outside. Let's give an optional argument to our initializer:

hello_view.rb
require 'mustache'
 
class HelloView < Mustache
  def initialize(user_name = 'World')
    @user_name = user_name
  end
 
  def user_name
    @user_name
  end
end

Now we can also update our other path:

app.rb
require 'sinatra'
 
require_relative 'hello_view'
 
get '/' do
  HelloView.new.render
end
 
get '/hello/:name' do
  HelloView.new(params[:name]).render
end

(I also deleted the path for saying goodbye, since we won't need that anymore.)

Now you can restart Sinatra, and open localhost:4567/hello/Heisenberg to say hello to Walter White.

I'll commit again: git commit -a -m 'Mustache template with parametrized user name'. Download here.

Refactoring

Usually code is changed incrementally. Sometimes this can lead to ugly code, or even just non idiomatic code. It's important to clean up the mess we have made. In our case it's not that big of a mess yet, but I did spot something that is not idiomatic ruby in our code, namely the function user_name in out HelloView. Usually when all a function does is to return a member with the same name, we prefer to use the attr_reader function to generate that for us. Here is the refactored version of `hello_view.rb`:

app.rb
require 'mustache'
 
class HelloView < Mustache
  attr_reader :user_name
 
  def initialize(user_name = 'World')
    @user_name = user_name
  end
end

Not a big change, but a nice first refactoring. Let's mark this with a commit in our git history: git commit -a -m 'The first refactoring'. Download here.

Sharing the result through GitHub

This concludes the first part of this tutorial, but before we move on, let's publish the code we have written on GitHub. If you do not have a GitHub account yet, you will need to register first. After that you will need to add an SSH key on the profile settings page. GitHub also has a tutorial on how to generate an SSH key.

Once you have set up your GitHub account, you can create an empty repository, with the name web-app-tutorial. You can leave the rest as is (public repository, no README.md, no .gitignore and no licence) and click "Create repository". GitHub tells us about 3 ways to start using the repository, we will choose the last one, i.e. pushing our existing local repository to the remote GitHub repository.

Open the terminal, and go to your project directory if you are not already there. Type git remote add origin git@github.com:<your git username>/web-app-tutorial.git, but substitute your username in place of <your git username>. This will add the GitHub repository as the origin remote. The origin remote is the default place to where we can publish our code. Now let's push what we already have: git push -u origin master

Now you can refresh the GitHub page in your browser, and you will see the code you have just pushed. You can also take a look at my version.

After that every time we wish to publish our new changes all we have to do is: git push

Summary

We have gotten really far in a really short part, so don't worry, if you don't get it all yet. We have started to use bundler as our dependency management tool, git as our version manager, and we have even pushed our code to GitHub. We took a glimpse at how to set up pages with Sinatra, and how to use mustache templates, but all we can do so far, is to say Hello. Don't worry, we will explore all of these technologies in more depth in our next part.