Neu: Das englische Ruby on Rails 4.0 Buch.

8.2. Sessions

Da HTTP ein zustandsloses (stateless) Protokoll ist, treffen wir auf besondere Probleme bei der Entwicklung von Anwendungen. Eine einzelne Webseite hat nichts mit einer anderen Webseite zu tun und sie wissen auch nichts voneinander. Da man sich aber auf vielen Webseite z. B. nur einmal und nicht auf jeder Seite neu anmelden will, ist das ein Problem. Die Lösung dafür heißt Session und wird von Rails dem Programmierer transparent als session[] Hash angeboten. Von Rails wird dabei automatisch für jeden neuen Besucher der Webseite eine neue Session angelegt. Diese wird per Default als Cookie (siehe Abschnitt 8.1, „Cookies“) gespeichert und unterliegt damit der Begrenzung auf 4 kB. Man kann die Sessions aber auch in der Datenbank speichern (siehe „Sessions in der Datenbank speichern“). Eine eigenständige und eindeutige (unique) Session-ID wird automatisch erstellt und der Cookie wird per Default beim Schließen des Webbrowsers gelöscht.

Warnung

Bitte beachten Sie, dass nur von Rails generierte Seiten im Session-Management arbeiten. Wenn Sie eine statische HTML-Seite aus /public aufrufen, so läuft das außerhalb des Session-Managements.
Das Schöne an einer Rails-Session ist, dass wir dort nicht nur wie bei Cookies Strings, sondern auch Hashes und Arrays abspeichern können. So lässt sich beispielsweise bequem ein Warenkorb in einem Online-Shop realisieren.

Breadcrumbs per Session

Als Beispiel erstellen wir eine Applikation mit einem Controller und drei Views. Beim Besuch eines Views werden die vorher besuchten Views in einer kleinen Liste angezeigt.
Die Basisapplikation:
MacBook:~ xyz$ rails new breadcrumbs
[...]
MacBook:~ xyz$ cd breadcrumbs
MacBook:breadcrumbs xyz$ rails generate controller Home ping pong index
[...]
MacBook:breadcrumbs xyz$
Als Erstes erstellen wir eine Methode, mit der wir die letzten drei URLs in der Session speichern und setzen eine Instanz-Variable @breadcrumbs, um die Werte im View sauber abrufen zu können. Dazu richten wir einen before_filter in der app/controllers/home_controller.rb ein:
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)
    @breadcrumbs.shift if @breadcrumbs.count > 3
    session[:breadcrumbs] = @breadcrumbs
  end
  
end
Jetzt benutzen wir die app/views/layouts/application.html.erb, um diese letzten Einträge am Kopf jeder Seite anzuzeigen:
<!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>Letzte Seitenaufrufe:</p>
  <ul>
    <% @breadcrumbs.each do |breadcrumb| %>
      <li><%= link_to breadcrumb, breadcrumb %></li>
    <% end %>
  </ul>
<% end %>

<%= yield %>

</body>
</html>
Jetzt können Sie mit rails server den Rails-Server starten und auf http://0.0.0.0:3000/home/ping, http://0.0.0.0:3000/home/ping oder http://0.0.0.0:3000/home/index surfen und oben immer sehen, auf welchen Seiten Sie vorher waren. Natürlich geht das erst auf der zweiten Seite, weil Sie beim ersten Aufruf ja noch keine Historie haben.
Aufruf der dritten Seite in einer Session

reset_session

Es gibt immer mal wieder Situationen, in denen man eine Session zurücksetzen möchte (also die aktuelle Session löschen und eine neue, frische Session aufbauen). Bei einem Logout einer Web-Applikation wird so beispielsweise oft die Session zurückgesetzt. Das geht einfach und wir bauen das kurz in unsere Breadcrumb-Applikation (siehe „Breadcrumbs per Session“) ein:
MacBook:breadcrumbs xyz$ 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
MacBook:breadcrumbs xyz$
Der entsprechend erweiterte Controller app/controllers/home_controller.rb sieht dann so aus:
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)
    @breadcrumbs.shift if @breadcrumbs.count > 3
    session[:breadcrumbs] = @breadcrumbs
  end
  
end
Damit können Sie beim Aufruf der URL http://0.0.0.0:3000/home/reset die aktuelle Session löschen.

Wichtig

Wichtig ist es nicht nur reset_session aufzurufen, sondern auch noch die Instanz-Variable @breadcrumbs auf nil zu setzen. Sonst würden im View doch noch die alten Breadcrumbs erscheinen.

Sessions in der Datenbank speichern

Die ganzen Session-Daten in einem Cookie auf dem Browser des Users zu speichern, ist nicht immer optimal. Alleine das Limit von 4 kB kommt einem schnell mal in die Quere. Es ist aber kein Problem, denn wir können das Speichern der Session vom Cookie in die Datenbank umstellen. Dann wird natürlich immer noch die Session-ID in einem Cookie gespeichert, aber die ganzen restlichen Session-Daten in der Datenbank auf dem Server.
Zuerst verändern wir die Konfiguration des Session Stores in der Datei config/initializers/session_store.rb. Wir kommentieren den :cookie_store aus und nehmen das Kommentarzeichen vor dem :active_record_store heraus.
# Be sure to restart your server when you modify this file.

# Breadcrumbs::Application.config.session_store :cookie_store, key: '_breadcrumbs_session'

# Use the database for sessions instead of the cookie-based default,
# which shouldn't be used to store highly confidential information
# (create the session table with "rails generate session_migration")

Breadcrumbs::Application.config.session_store :active_record_store
Jetzt müssen wir noch die entsprechende Tabelle anlegen. Das geht mit rails generate session_migration und rake db:migrate:
MacBook:breadcrumbs xyz$ rails generate session_migration
      invoke  active_record
      create    db/migrate/20120530145134_add_sessions_table.rb
MacBook:breadcrumbs xyz$ rake db:migrate
==  AddSessionsTable: migrating ===============================================
-- create_table(:sessions)
   -> 0.0014s
-- add_index(:sessions, :session_id)
   -> 0.0005s
-- add_index(:sessions, :updated_at)
   -> 0.0006s
==  AddSessionsTable: migrated (0.0027s) ======================================

MacBook:breadcrumbs xyz$
Fertig. Nun müssen Sie mit rails server den Server wieder starten und Rails speichert alle Sessions in der Datenbank.

Autor

Stefan Wintermeyer