Skip to content

Commit

Permalink
split chapter: sessions
Browse files Browse the repository at this point in the history
  • Loading branch information
svenfuchs committed May 17, 2015
1 parent 1197340 commit 5d6a38e
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 154 deletions.
5 changes: 4 additions & 1 deletion data/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@
- listing_names
- using_post
- redirect
- sessions
- path: sessions
children:
- cookies
- sinatra_sessions
- validations
- resources
- path: exercises
Expand Down
142 changes: 1 addition & 141 deletions source/12-sessions.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,146 +29,6 @@ Basically.

However, of course there are ways to identify your users. We all know that we
can sign in to a web application, and it would recognize who we are. Right?
Often web applications use cookies for this.

A cookie is a little piece of information that a web application can send along
with a response, and that will be stored by the browser. From then on, when the
browser makes another request to the same application it will include the cookie
to the request, sending it back to the application.

For example, an HTTP response that sets a cookie for a user's prefered visual
theme could look like this:

```
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: theme=light
```

From now on, the browser would then include the cookie to subsequent requests:

```
GET /blog.html HTTP/1.1
Host: rubymonstas.org
Cookie: theme=light
```

This way the application could apply the "light" theme to the blog, because the
user has selected it in some previous request: Cookies are a way to persist
(keep) state (data) across multiple requests.

Building on top of cookies, even though HTTP itself does not have a concept of a
"session" (or conversation), Sinatra (just as basically any web application
tool or framework) supports sessions. A session is a way for a web application
to set a cookie that persists arbitrary data across multiple requests.

Let's have a look how this works.

Say we'd like to pass a message from our `post` route to the next request, saying
"Successfully stored the name [name]". After successfully storing the name to
the file, we'd like to pass a message to the `GET` request that the browser is
going to be redirected to.

We'd only want to display this message once right after the redirection from
the `post` route: when we'd reload the page it should be gone. This is called
transient state, and a session is a great place to keep it.

First, we need to enable the `:sessions` feature in Sinatra. You can do that
by adding this line above of your routes:

```ruby
enable :sessions
```

Now, we want to store the message in our `post` route:

```ruby
post "/hello" do
name = params[:name]
store_name("names.txt", name)
session[:message] = "Successfully stored the name #{name}."
redirect "/hello"
end
```

Ok, cool.

This will add a `Set-Cookie` header to the reponse which has a long, messy looking, encrypted
string as a value.

In my browser it looks like this:

```
Set-Cookie: rack.session=BAh7CUkiD3Nlc3Npb25faWQGOgZFVEkiRWI4OTdhMDJlNDBkMDFlNjcxNWUw%0AZGI1ZWU5MzQ0YTQyMjAzYjFiZTE2YzYxNzgwMWQxYjI3NzhiOWNhYTQ4YzUG%0AOwBGSSIJY3NyZgY7AEZJIiU2ZjdjN2Y0ZmM0MTdmMGJkNjBkNmY5MmQ1NDEx%0ANGQ4ZgY7AEZJIg10cmFja2luZwY7AEZ7B0kiFEhUVFBfVVNFUl9BR0VOVAY7%0AAFRJIi03NGNlNDIxYTczNjMwZDY3MWViNTlkYzIzN2YyN2M5NGU3ZWU4NTRm%0ABjsARkkiGUhUVFBfQUNDRVBUX0xBTkdVQUdFBjsAVEkiLTA3NjBhNDRjMzU0%0AODIxMzJjZjIyNDQyYTBkODhjMDhiYjg1NTYyNTAGOwBGSSIIZm9vBjsARkki%0ACGJhcgY7AFQ%3D%0A; path=/; HttpOnly
```

Wow. Ok, the name of the cookie gives us a hint that this is a session, and it
is managed by Rack, which is what Sinatra uses under the hood to persist the
session.

Luckily we do not need to understand how exactly it does this. All we need to
know is that we can now use this data in the next request (the `GET` request)
like so:

```ruby
get "/hello" do
@message = session.delete(:message)
@names = read_names
@name = params["name"]
erb :hello
end
```

I.e. we delete the key `:message` from our session (which is something very
similar to a Ruby hash). Deleting it will return the value that was stored on
this key, and we assign it to the instance variable `@message`, which makes it
available to our template.

That means we can now use the message in our view like so:

```erb
<% if @message %>
<p><%= @message %></p>
<% end %>
```

Let's try it out. Restart your application, and go to <a href="http://localhost:4567">http://localhost:4567</a>.
If you enter a name, and click submit you should then see something like this:

<img src="/assets/images/12-sessions_1.png">

When you reload the page the message should be gone.

How does this work?

In our `post` route we store the message to the session hash. This has is
something that Sinatra provides to us as developers. When we enable this
feature then Sinatra will, after every request, store this hash to a cookie
with the name `rack.session`, in the encrypted form that you saw above.

We say the hash is being <a href="http://en.wikipedia.org/wiki/Serialization">serialized</a>,
which is a fancy way of saying that it is turned into some kind of format that
can be stored in text form. Sinatra (actually, Rack, under the hood) then also
encrypts and signs this data, so it is safe to send it over the internet (in
case we keep any sensitive data in it).

Ok, so the `post` route includes the `Set-Cookie` header with this session
cookie into its response, and sends it to the browser. The browser will, from
now on, pass this cookie back the our application as part of every subsequent
request.

When our browser is now redirected to `GET` the same URL again, it passes the
cookie, and Sinatra will, because we have the `:sessions` feature enabled,
*deserialize* (i.e. decrypt and read) the session data, and put it into the
hash that is returned by the method `session`, so we can work with it.

In our `get` route we now simply always delete the key `:message` from the
session hash. Should anything be in there it will be assigned to the `@message`
instance variable, and the view will display it. If nothing's stored on the
session key, for example when we reload the page, then deleting the key will
simply return nil, and nothing will be displayed in the view.

Does that make sense?

Awesome :)
Often web applications use cookies for this.

28 changes: 28 additions & 0 deletions source/12-sessions/01-cookies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Cookies

A cookie is a little piece of information that a web application can send along
with a response, and that will be stored by the browser. From then on, when the
browser makes another request to the same application it will include the cookie
to the request, sending it back to the application.

For example, an HTTP response that sets a cookie for a user's prefered visual
theme could look like this:

```
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: theme=light
```

From now on, the browser would then include the cookie to subsequent requests:

```
GET /blog.html HTTP/1.1
Host: rubymonstas.org
Cookie: theme=light
```

This way the application could apply the "light" theme to the blog, because the
user has selected it in some previous request: Cookies are a way to persist
(keep) state (data) across multiple requests.

118 changes: 118 additions & 0 deletions source/12-sessions/02-sinatra_sessions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Sessions in Sinatra

Building on top of cookies, even though HTTP itself does not have a concept of a
"session" (or conversation), Sinatra (just as basically any web application
tool or framework) supports sessions.

A session is a way for a web application to set a cookie that persists
arbitrary data across multiple requests.

Let's have a look how this works.

Say we'd like to pass a message from our `post` route to the next request, saying
*"Successfully stored the name [name]"*. I.e. after storing the name to the
file, we'd like to pass a message to the `GET` request that the browser is
going to be redirected to.

We'd only want to display this message once right after the redirection from
the `post` route: when we'd reload the page it should be gone. This is called
transient state, and a session is a great place to keep it.

First, we need to enable the `:sessions` feature in Sinatra. You can do that
by adding this line above of your routes:

```ruby
enable :sessions
```

Now, we want to store the message in our `post` route:

```ruby
post "/hello" do
name = params[:name]
store_name("names.txt", name)
session[:message] = "Successfully stored the name #{name}."
redirect "/hello"
end
```

Ok, cool.

This will add a `Set-Cookie` header to the reponse which has a long, messy looking, encrypted
string as a value.

In my browser it looks like this:

```
Set-Cookie: rack.session=BAh7CUkiD3Nlc3Npb25faWQGOgZFVEkiRWI4OTdhMDJlNDBkMDFlNjcxNWUw%0AZGI1ZWU5MzQ0YTQyMjAzYjFiZTE2YzYxNzgwMWQxYjI3NzhiOWNhYTQ4YzUG%0AOwBGSSIJY3NyZgY7AEZJIiU2ZjdjN2Y0ZmM0MTdmMGJkNjBkNmY5MmQ1NDEx%0ANGQ4ZgY7AEZJIg10cmFja2luZwY7AEZ7B0kiFEhUVFBfVVNFUl9BR0VOVAY7%0AAFRJIi03NGNlNDIxYTczNjMwZDY3MWViNTlkYzIzN2YyN2M5NGU3ZWU4NTRm%0ABjsARkkiGUhUVFBfQUNDRVBUX0xBTkdVQUdFBjsAVEkiLTA3NjBhNDRjMzU0%0AODIxMzJjZjIyNDQyYTBkODhjMDhiYjg1NTYyNTAGOwBGSSIIZm9vBjsARkki%0ACGJhcgY7AFQ%3D%0A; path=/; HttpOnly
```

Wow. Ok, the name of the cookie gives us a hint that this is a session, and it
is managed by Rack, which is what Sinatra uses under the hood to persist the
session.

Luckily we do not need to understand how exactly it does this. All we need to
know is that we can now use this data in the next request (the `GET` request)
like so:

```ruby
get "/hello" do
@message = session.delete(:message)
@names = read_names
erb :hello
end
```

I.e. we delete the key `:message` from our session (which is something very
similar to a Ruby hash). Deleting it will return the value that was stored on
this key, and we assign it to the instance variable `@message`, which makes it
available to our template.

That means we can now use the message in our view like so:

```erb
<% if @message %>
<p><%= @message %></p>
<% end %>
```

Let's try it out. Restart your application, and go to <a href="http://localhost:4567">http://localhost:4567</a>.
If you enter a name, and click submit you should then see something like this:

<img src="/assets/images/12-sessions_1.png">

When you reload the page the message should be gone.

How does this work?

In our `post` route we store the message to the session hash. This has is
something that Sinatra provides to us as developers. When we enable this
feature then Sinatra will, after every request, store this hash to a cookie
with the name `rack.session`, in the encrypted form that you saw above.

We say the hash is being <a href="http://en.wikipedia.org/wiki/Serialization">serialized</a>,
which is a fancy way of saying that it is turned into some kind of format that
can be stored in text form. Sinatra (actually, Rack, under the hood) then also
encrypts and signs this data, so it is safe to send it over the internet (in
case we keep any sensitive data in it).

Ok, so the `post` route includes the `Set-Cookie` header with this session
cookie into its response, and sends it to the browser. The browser will, from
now on, pass this cookie back the our application as part of every subsequent
request.

When our browser is now redirected to `GET` the same URL again, it passes the
cookie, and Sinatra will, because we have the `:sessions` feature enabled,
*deserialize* (i.e. decrypt and read) the session data, and put it into the
hash that is returned by the method `session`, so we can work with it.

In our `get` route we now simply always delete the key `:message` from the
session hash. Should anything be in there it will be assigned to the `@message`
instance variable, and the view will display it. If nothing's stored on the
session key, for example when we reload the page, then deleting the key will
simply return nil, and nothing will be displayed in the view.

Does that make sense?

Awesome :)

4 changes: 2 additions & 2 deletions source/13-validations.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,8 @@ We can fix this by changing our view like so:

Awesome.

With this completed you have now walked through an important pattern, which
also is used in Rails applications by default:
With this completed you have now walked through an important pattern for web
applications, which also is used in Rails applications by default:

* There is an HTML form which is being retrieved via a `GET` request.
* This form posts to another route, which validates the submitted data.
Expand Down
11 changes: 7 additions & 4 deletions source/14-resources.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# Resources

In HTTP a "resource" is something that is identified by a URL. In Rails it is
something slightly different: a collection of 7 routes that belong together in
the sense that they allow listing, viewing, and managing a collection of
things.
In HTTP a "resource" is something that is identified by a URL.

In Rails it is something slightly different: In Rails a resource a collection
of 7 routes that belong together in the sense that they allow listing, viewing,
and managing a collection of things. It is, essentially, a convention of
grouping routes, which has been encoded in Rails, but also is being used in
other applications, and generally a great practice.

Let's have a look at an example.

Expand Down
Binary file modified source/assets/images/12-sessions_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 4 additions & 6 deletions source/assets/stylesheets/_style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -90,18 +90,16 @@ code:before, code:after {
table {
margin-top: 1em;
width: 100%;

tr {
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
}
border-collapse: collapse;

th {
border: 1px solid #ddd;
text-align: left;
}

td, th {
padding: 0.5em 1em 0.5em 0;
border: 1px solid #ddd;
padding: 0.5em 1em 0.5em 1em;
}
}

Expand Down
1 change: 1 addition & 0 deletions source/layouts/layout.erb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

<footer>
<ul>
<li><a href="http://ruby-for-beginners.rubymonstas.org">Ruby For Beginners</a></li>
<li><a href="http://rubymonstas.org">Ruby Monstas</a></li>
<li><a href="mailto:[email protected]">Email</a></li>
<li><a href="https://twitter.com/rubymonsters">Twitter</a></li>
Expand Down

0 comments on commit 5d6a38e

Please sign in to comment.