Sinatra Data Persistence Using Only Params 1 — Sinatra Battleships Project Part 4

In my previous post, I explained the basics of Sinatra — how to set up your first GET route, making use of embedded Ruby templates and the erb Sinatra method, and how to start a local server running on your computer. I also began to examine the multi-use params hash that Sinatra offers.

In this post, we’ll delve much deeper into the neat tricks you can utilise with the params hash, to enable you to carry user input data through different HTTP requests.

HTTP is a Stateless Protocol

Firstly, let’s ask the question — why is it difficult to make data persist across different HTTP requests? In a typical Ruby console program your instance variables are accessible over an entire class, even an entire program.

Not so with HTTP requests. It is a stateless protocol which means that no information is retained by sender or receiver — i.e. they are ignorant of the state of one another. It “treats each request as an independent transaction that is unrelated to any previous request so that the communication consists of independent pairs of request and response.”

This means that there is no immediately obvious way of enabling routes in your app file to share data. Every time you make a new HTTP request, the previous data (local and instance variables which have been instantiated or added to) is ‘reset’ — the request is brand new, isolated and with no knowledge of what came before. So if you have two GET request routes in your app file, you can set @this_variable to "Sammy" in one, and you will not be able to call the variable or reference it in any way in the second.

But there are ways of sharing data between routes using only the params Sinatra hash. Let’s create a website that asks for a user’s name and age and then displays it back to them on a final page.

Input Forms

HTML input forms enable a user to send data with an HTTP request to the web server. In Sinatra, this information is captured within the params hash which I will show you how to utilise.

First, create a get route in an ‘app.rb’ file in your project’s root directory:

get '/first_name' do
erb :first_name
end

Create the ‘first_name.erb’ template in a Views directory as follows:

<form>
<input type="text">
<input type="submit">
</form>

This is HTML code which creates an input form used to enable the user to submit data. The ‘text’ input type creates an input box and the ‘submit’ input type creates a submit button.

Let’s go to ‘localhost:4567/first_name’ to see the input field and button. If you enter anything and hit ‘Submit’ now, nothing will seem to happen except that a question mark will be appended to the URL and the text in the textbox will disappear.

Clicking a ‘submit’ input element has the effect of submitting the form data (all of the text input boxes you have created and anything else) to the server by way of an HTTP request. If nowhere is specified for the data to be posted to, then the browser will submit it to the file path of the URL you are already on (in our case: ‘/first_name’). And if no request type is specified, the default type is GET. Therefore, by entering, say, ‘Clare’, into the form then pressing ‘Submit’, the browser will make a GET request at the same resource path that we are already on, but this time sending the string ‘Clare’ as input data.

If data is sent along with a GET request, a question mark is appended to the URL of the path, with the data following, which will show up as key/value pairs. However, our form input text element does not have a name, so there is no name for the key for the value ‘Clare’ to be assigned to. Therefore in our example, a naked question mark appears without any data following it.

Whenever you see a question mark with some data separated by ‘=’ and potentially also ‘&’, this means that some data has been sent with the HTTP request. These are called query parameters and are used extensively by websites, as we will see later on.

Let’s specify the path that the form should submit to by adding the action attribute to the form tag. We can also give our textbox a name, so that the value will be able to connect with a named key.

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

Restart the server (unless you are using a reloader — see my previous post) and refresh the page. Now enter your name in the textbox then press submit. ‘?first_name=your_name’ should now appear at the end of the URL. Your first query parameter is therefore displayed. If you reenter a different name and click submit, the name will change accordingly. If you add a second textbox to the input form and resubmit two names, the query parameters will all appear in order joined by ‘&’.

Query parameters reveal the data that has been added to the params hash by the submission of an input form. We can use params and input forms to persist this data across webpages.

Using params to make data persist

So, submitting an input form makes an HTTP request and sends the form’s data along with it. If no request type is specified, this will default to a GET request. The action attribute allows us to specify the resource path of the GET request. The form’s data is saved in the params hash, with the key set to the name attribute and the value set to whatever was entered into the textbox, which will be displayed as key/value query parameters at the end of the URL of the response webpage.

Putting all this together, we can make data persist across webpages for as long as we have space in the URL — it is worth noting that this is in fact limited, which is one of the reasons why this method of data persistence is only used for certain things.

At the top of the ‘first_name.erb’ file, add some code to tell the user to input their name.

Once the user has entered their name and clicked ‘Submit’, it would be useful to take them straight to the next webpage. We can do this by amending the form in the ‘first_name.erb’ file so that it submits the data to a second webpage called ‘age.erb’ — by changing the value of the action attribute to "/age".

Now, create the ‘age.erb’ file in the Views folder and copy over the content of the first_name template, replacing ‘age’ for ‘first name’ where it is sensible to do so.

We will also need to create a new route in our app file to handle the GET request to the age.erb file as follows:

get "/age" do
erb :age
end

Now, let’s open our first page again at ‘localhost:4567/first_name’. If you enter the name ‘Clare’ and click submit, a GET request will be made at the ‘/age’ resource path, but this time sending along with it the key/value pair first_name and 'Clare' within the params hash.

You can reference params[:first_name] using embedded Ruby code in your age.erb file to pull up the value that the user entered as their first name and display it back to the user wherever you like (‘Thanks first_name!’).

The Input Type ‘Hidden’

There is also a way of keeping the data through to a third webpage.

If you follow the same steps above to create a new ‘results.erb’ file as the resource path of the input form in the age.erb file, you will find that you are no longer able to display the user’s first name data from the first page, and that it will have disappeared as a query parameter.

This is because the data has been dropped when the form on the age webpage is submitted and you are taken to the results webpage. This is logical — you have not submitted the first_name data along with the new GET request to the results page — only the value saved in the new params[:age] key. As discussed above, each new HTTP request is made in insolation and does not retain what came before.

However, there is a way of retaining the first_name data through to the job webpage without the user being required to reenter it. You can use the hidden form input type and specify both the key and value (using thename and value attributes) within the input form in the age.erb file:

<form action="/results">
<input type="hidden" name="first_name" value="params[:first_name]">
<input type="text" name="age">
<input type="submit">
</form>

Now, the params first_name data will be submitted along with the age the user enters in the input box so that you are able to make use of it on the results webpage as well — e.g. you could say: ‘Thanks! I’ve discovered that your name is [first_name] and your age is [age]!’.

In my next blog post, we’ll look at how to make data persist using only params and query parameters.

--

--