8.2. Sessions

As HTTP is a stateless protocol, we encounter special problems when developing applications. An individual web page has nothing to do with another web page and they do not know of one another. But as you want to register only once on many websites, not over and over again on each individual page, this can pose a problem. The solution is called session and Rails offers it to the programmer transparently as session[] hash. Rails automatically creates a new session for each new visitor of the web page. This session is saved by default as cookie (see Section 8.1, “Cookies”) and so it is subject to the 4 kB limit. But you can also store the sessions in the database (see the section called “Saving Sessions in the Database”). An independent and unique session ID is created automatically and the cookie is deleted by default when the web browser is closed.

Warning

Please note that only pages generated by Rails work in the session management. If you go to a static HTML page from /public, this takes place outside of the session management.
The beauty of a Rails session is that we can not just save strings there as with cookies, but also hashes and arrays. So you can for example use it to conveniently implement a shopping cart in an online shop.

Breadcrumbs via Session

As an example, we create an application with a controller and three views. When a view is visited, the previously visited views are displayed in a little list.
The basic application:
$ rails new breadcrumbs
  [...]
$ cd breadcrumbs 
$ rails generate controller Home ping pong index
      create  app/controllers/home_controller.rb
       route  get "home/index"
       route  get "home/pong"
       route  get "home/ping"
      invoke  erb
      create    app/views/home
      create    app/views/home/ping.html.erb
      create    app/views/home/pong.html.erb
      create    app/views/home/index.html.erb
      [...]
$ 
First we create a method with which we can save the last three URLs in the session and set an instance variable @breadcrumbs, to be able to neatly retrieve the values in the view. To that end, we set up a before_filter in the app/controllers/home_controller.rb:
class HomeController < ApplicationController
  before_filter :set_breadcrumbs
  
  def ping
  end
  
  def pong
  end
  
  def index
  end
  
  private
  
  def set_breadcrumbs
    @breadcrumbs = session[:breadcrumbs]
    @breadcrumbs ||= Array.new
    @breadcrumbs.push(request.url)
    if @breadcrumbs.count > 3
      @breadcrumbs.shift
    end
    session[:breadcrumbs] = @breadcrumbs
  end
end
Now we use the app/views/layouts/application.html.erb to display these last entries at the top of each page:
<!DOCTYPE html>
<html>
<head>
  <title>Breadcrumbs</title>
  <%= stylesheet_link_tag    "application", :media => "all" %>
  <%= javascript_include_tag "application" %>
  <%= csrf_meta_tags %>
</head>
<body>

<% if @breadcrumbs %>
  <p>Last pages visited:</p>
  <ul>
    <% @breadcrumbs.each do |breadcrumb| %>
      <li><%= link_to breadcrumb, breadcrumb %></li>
    <% end %>
  </ul>
<% end %>

<%= yield %>

</body>
</html>
Now you can start the Rails server with rails server and go to http://0.0.0.0:3000/home/ping, http://0.0.0.0:3000/home/ping or http://0.0.0.0:3000/home/index and at the top you will then always see the pages that you have visited before. Of course, this only works on the second page, because you do not yet have a history on the first page you visit.
Visiting the third page in a session

reset_session

Occasionally, there are situations where you want to reset a session (in other words, delete the current session and start again with a new, fresh session). For example, if you log out of a web application, the session will be reset. This is easily done and we can quickly integrate it into our breadcrumb application (see the section called “Breadcrumbs via Session”):
$ rails generate controller Home reset -s
        skip  app/controllers/home_controller.rb
       route  get "home/reset"
      invoke  erb
       exist    app/views/home
      create    app/views/home/reset.html.erb
      invoke  test_unit
        skip    test/functional/home_controller_test.rb
      invoke  helper
   identical    app/helpers/home_helper.rb
      invoke    test_unit
   identical      test/unit/helpers/home_helper_test.rb
      invoke  assets
      invoke    coffee
   identical      app/assets/javascripts/home.js.coffee
      invoke    scss
   identical      app/assets/stylesheets/home.css.scss
$
The correspondingly expanded controller app/controllers/home_controller.rb then looks like this:
class HomeController < ApplicationController
  before_filter :set_breadcrumbs
  
  def ping
  end
  
  def pong
  end
  
  def index
  end

  def reset
    reset_session
    @breadcrumbs = nil
  end
  
  private
  
  def set_breadcrumbs
    @breadcrumbs = session[:breadcrumbs]
    @breadcrumbs ||= Array.new
    @breadcrumbs.push(request.url)
    if @breadcrumbs.count > 3
      @breadcrumbs.shift
    end
    session[:breadcrumbs] = @breadcrumbs
  end
end
So you can delete the current session by going to the URL http://0.0.0.0:3000/home/reset.

Important

It is not just important to invoke reset_session, but you need to also set the instance variable @breadcrumbs to nil. Otherwise, the old breadcrumbs would still appear in the view..

Saving Sessions in the Database

Saving the entire session data in a cookie on the user's browser is not always the best solution. Amongst others, the limit of 4 kB can pose a problem. But it's no big obstacle, we can relocate the storing of the session from the cookie to the database. Then the session ID is of course still saved in a cookie, but the whole other session data is stored in the database on the server.
First, we change the configuration of the session store in the file config/initializers/session_store.rb. We comment out the :cookie_store and take out the comment sign before the :active_record_store.
Breadcrumbs::Application.config.session_store :active_record_store
Now we still need to create the corresponding table. We do it with rails generate session_migration and rake db:migrate:
$ rails generate session_migration
  [...]
$ rake db:migrate
  [...]
$
Job done. Now you need to start the server again with rails server and Rails saves all sessions in the database.

Updates about this book will be published on my Twitter feed.