Sinatra Data Persistence Using Sessions and Cookies— Sinatra Battleships Project Part 6

In my previous posts, I examined how to enable data persistence in Sinatra using only the params hash and query parameters.

In this post, we’ll take a look at Sinatra’s sessions hash and how this enables data persistence across requests with ease.

Cookies

Cookies are simple text files which usually contain three items of data: the name of the server the cookie was sent from, the lifetime of the cookie and a value — usually a randomly generated unique number.

When you visit a site for the first time, if that site makes use of cookies then after confirming your permission to do so, a cookie is downloaded onto your computer in your browser directory or program data subfolders. The next time you visit that site, your computer checks to see if it has a cookie that is relevant (i.e. one that contains the site name) and if it does, it sends the information contained in the cookie along with each browser request, which enables the web application to identify you. Only the server which sent the cookie can read it.

Note that cookies themselves do not contain user information — except for the three items above. Without the website itself, therefore, cookies themselves are essentially anonymous and do not contain confidential information. It is the web application that records the substantive information (user credentials, preferences, etc.) on its own database against the unique number of a cookie. The cookie is essentially just a unique identifier, which enables the application to link the information it holds about you to you.

Types of Cookies

There are two types of cookies: session cookies and persistent, tracking or permanent cookies.

Session cookies are created temporarily in your browser’s subfolder only for the duration of your visit to the website, after which the session cookie is deleted. After your login details have been matched within the web application’s database, the website keeps track of you from page to page using a session cookie so that you do not have to reauthenticate your details with every page. This is also how item selection works — such as adding items to a shopping cart — among other things.

Persistent cookies are saved to your computer’s long-term storage and activated whenever you visit the website that created the cookie. They endure for the lifetime stipulated on the cookie, after which they are automatically deleted. Persistent cookies may be used to track a user’s language and layout preferences and enable automatic log-in, so that you do not have to reenter your details on your next visit to a website. So, the persistent cookie ‘automatically’ logs you in, but then the session cookie keeps track of you as you traverse the website.

These days, websites and programs can create sophisticated profiles about you based on your internet habits. Cookie profiling is where the administrators of popular websites sell cookie data to marketers. The marketers collect information from cookies about you from various sources to create a user profile about you so they can use targeted marketing on websites.

It is not the purpose of this post to decree on the merits or misgivings of cookies — what is important to us is that we can use session cookies to enable data persistence across a user session.

Sessions in Software

As per Wikipedia, computing, a session is an interactive information interchange and is also referred to as a dialogue, conversation or meeting, between two or more communicating devices, or between a computer and a user.

A session is typically stateful, meaning that at least one of the communicating parts needs to save information about the session history in order to be able to communicate — as opposed to a stateless communication, where the communication consists of independent requests with responses, such as HTTP (above).

As a workaround for the lack of a session layer in HTTP, HTTP servers implement various session management methods, typically utilising a unique identifier in a cookie or parameter that allows the server to track requests originating from the same client, and effectively creating a stateful protocol on top of HTTP.

In other words, the session cookie is sent by each new browser request made by the user, clicking through the webpages of a website, to the website via the web server, which retains the session information. In this manner, a stateful session is effectively enabled.

Let’s work through a short example to see how sessions work in Sinatra, including use of the HTTP POST request type.

HTTP Post Requests

In previous posts, we have learnt about HTTP GET requests, where a browser says to the web server — please retrieve and send me this or that webpage.

An HTTP POST request is where the browser says to the server: here is some data that I am submitting to you.

The following form outline is contained in the first_name.erb file of our first web application here:

<form action="/age">
<input type="text" name="first_name">
<input type="submit">
</form>

The submit input type creates a button, which when pressed by the user will trigger a new HTTP request. The action attribute describes the resource path and the default HTTP method used is GET. It is in fact a bit naughty to use a GET request here — the HTTP specification says that these should be safe — which means that they should not create change in the application or resource, they should only be retrieving webpages.

So let’s change the method to POST, whose purpose is indeed to submit data and make change to the application.

<form action="/age" method="post">.

Now if we go to ‘localhost:4567/first_name’ and submit our name in the box, Sinatra’s version of a 404 page will appear.

This is because we have not defined what should happen if a POST request is made at path ‘/age’. So let’s create a route in our app file:

post '/age' do
erb :post_first_name
end

We haven’t yet created the template for the post page so let’s create ‘post_first_name.erb’ in Views with the following content:

<p>Ok, thanks <%= params[:first_name] %></p><form action="/age">
<input type="hidden" name="first_name" value=<%= params[:first_name]%>>
<button type="submit">Click here to go to the next page!</button>
</form>

You can create a button to take the user to the next page as I’ve done above if you like, and you can persist the first_name data using the hidden data type as we’ve done previously.

You’ll also notice that there are no query parameters included in the URL at the post_first_name webpage. This is due to technical reasons in relation to the different way that GET and POST requests submit data and parameters — for our purposes, essentially GET requests include the parameters in the URL and POST requests make document submissions in the content body of the request.

Sessions in Sinatra

Let’s try out the Sinatra sessions hash, which works by using cookies as outlined above.

First, enable the sessions feature at the top of the app file we have been using thus far: enable :sessions.

Let’s amend our first_name.erb file to read as follows:

<form action="/first_name" method="post">
<input type="text" name="first_name">
<input type="submit">
</form>

When the user inputs their name and clicks submit, a POST request will be made at path ‘/first_name’, with the ‘first_name => name_entered’ key/value pair.

Let’s make a new POST route in our app file:

post '/first_name' do
session[:first_name] = params[:first_name]
redirect '/age'
end

Here, we are saving the value of the first_name in params in the sessions hash with the same key name. We are now able to access this in any other route. redirect means that a GET request should be made at the path stipulated.

And at the age GET route, we simply need to add session[:first_name] in the body of the route in order to have access to it within the template we pass in.

One neat additional feature, which is useful if you wish to create a transient state in which a state is persisted for only one webpage and then deleted, you could use the following code:

get '/age' do
@first_name = session.delete(:first_name)
erb :age
end

The delete call will obviously delete the key/value pair within the sessions hash — but it also returns the value, which means that you are able to retain this in an instance variable for one-time use within that route, after which the data will be gone for subsequent requests.

This would be useful if you wanted to display a message (such as ‘Thanks [first_name]!’) only once, but then wanted it to disappear after that, rather than being re-displayed with each refresh.

--

--