8.2. Sessions

As HTTP is a stateless protocol, we encounter special problems when developing applications. An individual web page has no connection to the next 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 a 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.
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
  [...]
$ 
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
    if session[:breadcrumbs]
      @breadcrumbs = session[:breadcrumbs]
    else
      @breadcrumbs = Array.new
    end

    @breadcrumbs.push(request.url)

    if @breadcrumbs.count > 4
      # shift removes the first element
      @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:
<html>
<head>
  <title>Breadcrumbs</title>
  <%= stylesheet_link_tag    "application", media: "all", "data-turbolinks-track" => true %>
  <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
  <%= csrf_meta_tags %>
</head>
<body>

<% if @breadcrumbs && @breadcrumbs.count > 1 %>
  <h3>Surf History</h3>
  <ul>
    <% @breadcrumbs[0..2].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/controllers/home_controller_test.rb
      invoke  helper
   identical    app/helpers/home_helper.rb
      invoke    test_unit
   identical      test/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
    if session[:breadcrumbs]
      @breadcrumbs = session[:breadcrumbs]
    else
      @breadcrumbs = Array.new
    end

    @breadcrumbs.push(request.url)

    if @breadcrumbs.count > 4
      # shift removes the first element
      @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.

Buy the new Rails 5.1 version of this book.

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 with the Active Record Session Store gem (https://github.com/rails/activerecord-session_store). 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.
To install the gem we have to add the following line at the end of the file Gemfile
gem 'activerecord-session_store', github: 'rails/activerecord-session_store'
After that we have to run bundle install
$ bundle install
Fetching git://github.com/rails/activerecord-session_store.git
remote: Counting objects: 134, done.
remote: Compressing objects: 100% (89/89), done.
remote: Total 134 (delta 48), reused 112 (delta 32)
Receiving objects: 100% (134/134), 24.33 KiB, done.
Resolving deltas: 100% (48/48), done.
Fetching gem metadata from https://rubygems.org/..........
Fetching gem metadata from https://rubygems.org/..
Resolving dependencies...
Using rake (10.1.0) 
[...]
$ 
After that we have to run rails generate active_record:session_migration and rake db:migrate to create the needed table in the database.
$ rails generate active_record:session_migration
      create  db/migrate/20130717160101_add_sessions_table.rb
SW:breadcrumbs stefan$ rake db:migrate
==  AddSessionsTable: migrating ===============================================
-- create_table(:sessions)
   -> 0.0014s
-- add_index(:sessions, :session_id)
   -> 0.0003s
-- add_index(:sessions, :updated_at)
   -> 0.0004s
==  AddSessionsTable: migrated (0.0023s) ======================================

$
After that we'll have to change the session_store in the file config/initializers/session_store.rb to :active_record_store.
Breadcrumbs::Application.config.session_store :active_record_store
Job done. Now you need to start the server again with rails server and Rails saves all sessions in the database.

Thank you for your support and the visibility by linking to this website on Twitter and Facebook. That helps a lot!