Jan 05

I Did It My Way

Filed in: sinatra geeky tutorial ruby

This is the first project for my Sinatra Cookbook project - A new project every 2 months for the whole of this year.

Reverse - Your First Sinatra App

The first application we will build is called Reverse. It is a very simple application that allows users to enter some text and then it reverses the text for them. It will give you a good understanding of how Sinatra works.

To start with, make a folder called reverse that contains a file called main.rb.

Open up main.rb in your favourite text editor and enter the following code:

require 'rubygems'
require 'sinatra'

get '/' do
  "I did it my way!"
end

The top 2 lines are required to be in all Sinatra apps. It is the next part where the action starts. This called a handler because it handles routes and actions. The first part (get) states which http method is being used, in this case, GET, because we are 'getting' a page. The next part is a string that corresponds to the route. In this case the route is '/' - the root url of the application. Then there is a code block that specifies what happens when the user visits this url. The last line of this block is what is displayed on the page, so in this case "I did it my way!" wil be displayed on the page.

Let's test this out. First we need to start the application. Open up a terminal and navigate to the reverse folder, then type:

~/reverse$  ruby main.rb

You should see a message similar to the one below, saying that the server is starting:

== Sinatra/0.9.4 has taken the stage   on 4567 for development 
with backup from WEBrick
[2009-08-25 19:39:20] INFO  WEBrick 1.3.1
[2009-08-25 19:39:20] INFO  ruby 1.8.7 (2008-08-11) [i486-linux]
[2009-08-25 19:39:26] INFO WEBrick::HTTPServer#start: pid=12895 port=4567

Sinatra runs on port 4567 in development mode, so open up your browser and go to http://localhost:4567/

You should see the message "I did it my way!"

Screenshot 1

See the code on github

While you have the browser open, try going to http://localhost:4567/reverse. You should get the standard, nice looking Sinatra 404 error page, telling you that he doesn't know this ditty. This also gives you some advice about what route you should add to your file. In this case we should try:

get '/reverse' do
  "Hello World"
end

Screenshot 2

This is how easy Sinatra makes it to create actions based on different routes. But we don't want to create that route yet, so let's go back to our original '/' handler. It's all very well putting a message in this block, but you can't create great web pages with just a string. That's what views are for. Let's add something more substantial.

Open the main.rb file and add the following code:

require 'rubygems'
require 'sinatra'

get '/' do
  erb :home
end

use_in_file_templates!

__END__

@@ home

<h1>Reverse</h1>

<p>
Welcome to the home page of my very first Sinatra app.
</p>

What we've done now is instead of placing the string to be displayed in the block, we've told Sinatra to use the 'home' view, that is written in erb (embedded ruby). This view can be seen at the end of the file, denoted by @@ home. If you want to keep your views in the same file (which makes sense for small apps), you need to add the line use_in_file_templates! and the templates must come after the __END__ declaration. You can put your views into separate files, but more on that later.

Let's check out our new view. Go back to your terminal and if the server is still running, kill it by pressing 'ctrl' and 'c' together. Now enter ruby main.rb again and navigate back to http://localhost:4567 (or just refresh the page).

You should see your message, like the one below:

Screenshot 3

See the code on github

As I mentioned above, you don't have to keep your views all in the same file, so let's put them somewhere else.

Create a folder called 'views' inside the 'reverse' folder. Now create a file and save it as 'home.erb'. Copy the following code into this file:

<h1>Reverse</h1>

<p>
Welcome to the home page of my very first Sinatra app.
</p>

You can now delete all of the view code from main.rb, so it should look like this:

require 'rubygems'
require 'sinatra'

get '/' do
  erb :home
end

Nothing should have changed with your app, but let's test it. Kill and restart the server in the terminal (ctrl + c, then ruby main.rb, in case you've forgotten). When you refresh your browser, nothing should have changed.

See the code on github

You can go one step further and create a layout. This acts as a template that bookends all of your views. This is useful to save having to repeat code. Let's put all the boring html headers into a layout file. First of all, create a file in the 'views' folder and save it as 'layout.erb'. Now open it up in your favourite text editor and enter the following code:

<!DOCTYPE html>
<html lang="en">
<head>
<title>Reverse!</title>
<meta charset=utf-8 />
</head>
<body>
<h1>Reverse</h1>

<%= yield %>

<p>The first Sinatra project for I Did It My Way</p>
</body>
</html>

This is always rendered whenever a view is shown. Everythign except for the <%= yield %> bit. This is the point where the view is rendered. Let's make a slight change to the 'home' view and then test it out. Open up home.erb and change it to the following:

<h2>Home</h2>
<p>
Welcome to the home page. This app is going to be amazing....
</p>

Now restart the server and check out how it looks in your browser. Hopefully it will look something like this:

Screenshot 4

See the code on github

Notice that the Home heading and welcome message from 'home.erb' comes after the 'Reverse' heading, but before the paragraph about who created it. In other words it comes exactly where <%= yield %> is in the layout.

That funny tag with the percentage signs that yield comes in is an example of embedded ruby being used. You can add ruby to your html by placing it inside angled brackets like these <% .... %> This is very useful for things like if statements:

<% if something_happens %>
<h1>Something happened</h1>
<% else %>
<h1>Nothing happend</h1>
<% end %>

If you place some code inside angled brackets with an equals sign as well, like these <%= .... %> then the ruby code inside is evaluated and the result is displayed. For example you could do this.

<% title = "Reverse" %>
<h1>
<%= title %>
</h1>

The first bracket sets a variable called title and the second evaluates this variable, so a heading of 'Reverse' will be displayed. That example was a bit pointless, but there are loads of useful things you can do with embedded ruby.

Instance Variables

In fact, let's use some embedded ruby to set the title of our page. Go back into main.rb and add the following code:

require 'rubygems'
require 'sinatra'

get '/' do
  @title = "Home"
  erb :home
end

This sets a session variable (denoted by the @ sign at the beginning) called @title. Session variables are available to other parts of the code, including the views. So we can now refer to @title in the views and layout. Let's give this a go by entering the following code in home.erb:

<!DOCTYPE html>
<html lang="en">
<head>
<title>Reverse!</title>
<meta charset=utf-8 />
</head>
<body>
<h1>Reverse</h1>
<h2><%= @title %></h2>

<%= yield %>

<p>The first Sinatra project for I Did It My Way</p>
</body>
</html>

Now remove the hard-coded title in home.erb:

<p>
Welcome to the home page. This app is going to be amazing....
</p>

Restart the server and check that it all works (nothing should have changed). This now means that we can set the page's title in the handler. Let's test this out by creating a new route. Open up main.rb and enter the following code:

require 'rubygems'
require 'sinatra'

get '/' do
  @title = "Home"
  erb :home
end

get '/frank' do
  @title = "My Way"
  erb :home
end

Restart the server and go to http://localhost:4567/frank. You should see a very similar page, except the level 2 heading should read 'My Way' instead of 'Home', like in the screenshot below:

Screenshot 5

See the code on github

But we are still, seeing virtualy the same thing because we are using the same view. We can create a different view that will be displayed at this page. Create a file called 'frank.erb' in the 'views' folder and enter the following code:

<p>
And now, the end is here
And so I face the final curtain
My friend, I'll say it clear
I'll state my case, of which I'm certain
I've lived a life that's full
I traveled each and ev'ry highway
And more, much more than this, I did it my way
</p>

Now change the line that says erb :home, to erb :frank, restart the server and check out how your app is looking. You should now have 2 different pages, one at http://localhost:4567/ and the other at http://localhost:4567/frank.

Have a little play around at creating some more pages.

Going Postal

But all we have done so far is create some essentially static pages. The whole point of creating a web app is to make it interactive in some way. Now things are going to start to get interesting. First of all, we'll set things up by adding a form to the homepage. Open up home.erb and replace the existing code the following:

<form action="/reverse" method ="post" accept-charset="utf-8">
<input type="text" id="phrase" name="phrase" value="Write something...">
<input type="submit" value="...and reverse it!">
</form>

That's the form taken care of, but we need a new handler to deal with the form when it is submitted. If you have a look at the code, it tells the form to submit itself to the '/reverse' action, using the POST method. This is where you start to see just how nice Sinatra's syntax is - you might even guess how we write the new handler. Open up main.rb and enter the following code:

require 'rubygems'
require 'sinatra'

get '/' do
  @title = "Home"
  erb :home
end

post '/reverse' do
  params.inspect
end

The last line is the new handler to deal with the route '/reverse', this is defined as a POST route, meaning that it only reacts to http POST requests (basically when a form is submitted to the browser). But what's with the params.inspect thing? Params is a hash that contains all of the information that has been sent as parameters - either through a form or via the url. params.inspect shows the key/value pairs of all the parameters. Let's give it a go.

Open up your browswer and go to http://localhost:4567/. Fill in the form with any word and press submit. You should see a page showing the following:

Screenshot 6

This is telling us that the key phrase is set with the value of "Sinatra". The reason the key is set to phrase is because of the name attribute in the input field in the form. You can access any values set as parameters by referencing the params hash like so:

params[:phrase] This would produce "Sinatra"

Imagine the following form:

<form action="/reverse" method ="post" accept-charset="utf-8">
<input type="text" name="name">
<input type="text" name="email">
<input type="text" name="password">
<input type="submit" value="submit">
</form>

You could access the values from this form by using params[:name],params[:email] and params[:password].

Now we know how we to access parameters from forms, we can use the text parameter from our form. Try adding this code to main.rb:

require 'rubygems'
require 'sinatra'

get '/' do
  @title = "Enter Your text here"
  erb :home
end

post '/reverse' do
  @title = "Here's Your Reversed Text:"
  params[:phrase].reverse
end

Restart the server then go back to http://localhost:4567 and try submitting the form again. This time you should see the phrase you wrote, but backwards like in the screenshot below.

Screenshot 7

This is because the the last line of the method is params[:text].reverse. Remember the last line of a method is what is returned for that url. params[:phrase] is what was typed into the form and the .reverse on the end is the reverse method for strings of text which does what it says on the tin and writes the string backwards - just what our app is meant to do!

See the code on github

We can get even trickier and use the same url for both pages. This is because to view the form requires a GET request and the resulting reversed text from the submitted form is a POST request. This means that they can have the same route, but different handlers. Change the code in main.rb to the following:

require 'rubygems'
require 'sinatra'

get '/' do
  @title = "Enter Your text here"
  erb :home
end

post '/' do
  @title = "Here's Your Reversed Text:"
  params[:phrase].reverse
end

This time the url is the same ('/') but the http method is different (get and post respectively). This technique of posting to itself is called postback. Sinatra also supports the two other http methods, PUT and DELETE. We don't need these for this app, but will come across them in later projects.

You also need to change the form's action attribute, so that it posts the form to the correct url('/'), so the form in home.erb becomes:

<form action="/" method ="post" accept-charset="utf-8">
<input type="text" id="phrase" name="phrase" value="Write something...">
 <input type="submit" value="...and reverse it!">
</form>

Now let's tidy things up and create a view so that the results page looks nicer than just displaying the reversed text. First of all, let's edit main.rb:

require 'rubygems'
require 'sinatra'

get '/' do
  @title = "Enter Your text here"
  erb :home
end

post '/' do
  @title = "Here's Your Reversed Text:"
  @reversed_text = params[:phrase].reverse
  erb :reverse
end

First of all we have set the reversed phrase as an instance variable, so we can access it in the view. Then we render the reverse.erb view. Let's create that now. Open up a new document and save it as reverse.erb. Place the following code in that document:

<h3>Here is your reversed text......</h3>
<p><strong><%= @reversed_text %></strong></p>

This wil now display a message and the reversed text (don't forget to put it in the <%= %> brackets).

Screenshot 8

See the code on github

Named Parameters

We could stop here - our app does exactly what it should do. But we can do even more. The next step is to allow users to enter the word that they want reveresed directly into the url. But how do we find out what word they have entered? You do this by adding named parameters in your urls like so:

get '/:phrase' do

This will automatically add a parameter with the key of 'phrase' to the params hash. Whatever the user enters in the url can be accessed using params[:phrase]. So, for example, the url http://localhost:4567/frank will produce params[:phrase] = "frank". The url http://localhost:4567/sinatra will produce params[:phrase] = "sinatra". So now we can add an extra route to our code to deal with this. Open up main.rb and add the following code:

require 'rubygems'
require 'sinatra'

get '/' do
  @title = "Enter Your text here"
  erb :home
end

post '/' do
  @title = "Here's Your Reversed Text:"
  @reversed_text = params[:phrase].reverse
  erb :reverse
end

get '/:phrase' do
  @title = "Here's Your Reversed Text:"
  @reversed_text = params[:phrase].reverse
  erb :reverse
end

Let's check this out. Have a go at visiting some urls and check that the word is displayed backwards. Don't forget to restart the server first! It should look something like the screenshot below:

Screenshot 9

See the code on github

And that just about wraps up our first app. You can see the app running on heroku and view the source on github. Have a play around at adding a bit more functionality and add some style and navigation to the pages. The next project will use a database backend to create a simple to do list app.

Blog Posts

Tags

Pics from Flickr

Recent

Random

Listening to

The Killers: Sam's Town

Sams Town by the Killers 5

A great follow up album to their debut, Hot Fuss. Stand out tracks are Read My Mind and Sams Town.

Relaxing Reading

Labrynth by Kate Mosse

Labrynth book cover 3

Good summer reading, very much in the mould of The Da Vinci Code (but better writing). Another search for the grail and another wierd and secretive religous group.

Geeky Reading

Bulletproof Web Design by Dan Cederholm

Bulletproof Web Design book cover 5

An essential book for any web designer. This shows you how to create common web layouts using standard based layouts and avoid tables. It looks great and is full of priceless info.