Cookies are useful for keeping track of a piece of information between page changes, but as you may have noticed in Figures 13-3 and 13-5, Rails was already setting a cookie, a session cookie, with each request. Rather than manage cookies yourself, you can let Rails do all of that work and move one step further back from the details. (This example is available in ch13/guestbook012.)
Sessions are a means of keeping track of which user is making a given request. Rails doesn’t know anything specific about the user, except that he has a cookie with a given value. Rails uses that cookie to keep track of users and lets you store a bit of information that survives between page requests.
You can set and retrieve information about the current session
from the session
object, which is
available to your controller and view. Because it’s a better idea in
general to put logic into the controller, Example 13-3, which is a new
version of the app/controllers/entry_controller.rb
file, shows what’s involved in storing an array in the session
object, retrieving it, and modifying
it to add names to the list. Virtually all of it replaces code that was
in Example 13-1, with
only the retrieval of the name from the form staying the same.
Example 13-3. Working with an array stored in the session object
class EntryController < ApplicationController def sign_in #get names array from session @names=session[:names] #if the array doesn't exist, make one unless @names @names=[] end #get the latest name from the form @name = params[:visitorName] if @name # add the new name to the names array @names << @name end # store the new names array in the session session[:names]=@names end end
Most of the new code is about working with an array rather than a
simple field. It’s not a big problem if a string is empty, whereas
trying to add new entries to a nonexistent array is a bigger problem.
The sign_in
method gets the names
array from the session
object and puts it in @names
. If the session
object doesn’t have a names
object, it will return nil
, so the unless
creates a names
array if necessary. Then the method
retrieves the latest visitorName
from
the form and adds it to the @names
array. The very last line puts the updated version of the @names
array back into the session
object so that the next call will have
access to it.
Example 13-3 is
more verbose than it needs to be, as you could work on session[:names]
directly. However, it’s a
bit clearer to work with the @names
instance variable, and this approach lets the view work strictly with
instance variables.
The view requires fewer changes—just a test that the list of names exists and a loop to display the names if it does. The changes to app/views/entry/sign_in.html.erb are highlighted in Example 13-4.
Example 13-4. Reporting a set of previous names to the user
<html> <head><title>Hello <%=h @name %></title></head> <body> <h1>Hello <%=h @name %></h1> <% form_tag :action => 'sign_in' do %> <p>Enter your name: <%= text_field_tag 'visitorName', @name %></p> <%= submit_tag 'Sign in' %> <% end %><% if @names %>
<ul>
<% @names.each do |name| %>
<li><%=h name %></li>
<% end %>
</ul>
<% end %>
</body> </html>
As Figures 13-7 through 13-9 demonstrate, the application now remembers what names have been entered before.
If you quit your browser and return, or try a different browser,
you’ll get the empty result shown in Figure 13-8 again, as the
session changes. This application is very different from the application
at the end of Chapter 4, which stored
names from everyone in the same database. Because this application
relies on the session
object, only
the names entered in this browser at this time will appear. That session
identifier will vanish when the user quits their browser because the
session cookie will be deleted, and those names will no longer be
accessible.
The session
object builds on
the cookie functionality described in the previous section, but Rails
takes care of all the cookie handling. For simple applications, where
you’re just going to store something small in the session, you now know
everything you need to know and can skip ahead if you’d like.
There are, of course, more details, more things you can tweak.
First of all, you can turn sessions off if you have an application that
doesn’t need them and want a little speed boost. Just add session :off
at the top of controller classes or, for the whole
application, in app/controllers/application.rb. (You can
turn individual controllers back on with session :on
, and the documentation for ActionController::SessionManagement
shows many
more options for controlling when sessions are used.)
Just as with cookies, you can limit the use of sessions to secure
HTTPS connections. To do so, just start off with session :session_secure => true
. Sessions will stop working over regular HTTP connections
and only work when HTTPS is in use.
The hard question about sessions is where the data is actually stored. A key reason that HTTP is stateless is that it takes a lot of computing time to look up the state for every single transaction. Those queries can become a bottleneck, especially when you want to do things like distribute an application across multiple servers. Rails offers a number of options for solving those problems. There are only two you should consider when getting started, however. Both are illustrated in Figure 13-10.
The first, the CookieStore
,
is what Rails uses by default. All of the data that goes
into the session
object for a given
session is stored directly in the cookie Rails uses to track the
session. In some ways, this is extremely convenient—all the session
information comes with the request, and the users’ browsers become a
gigantic distributed data storage system for the Rails application. On
the other hand, this limits the overall storage to 4K, the limit of
cookie size, and it means that all of the session information is
constantly transferred back and forth in a simple and easily decrypted
hash. If you can accept the size limit and the openness, though, it’s
easy.
The second approach, the ActiveRecord SessionStore
, stores only an identifier token in the session cookie, and
stores the actual session data in the database. To make this work, you
need to make a change in the config/environment.rb file. First, find:
# 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 "rake db:sessions:create")
#
config.action_controller.session_store = :active_record_store
and then delete the #
marked in
bold. You’ll also have to, as the comment notes, run rake
db:sessions:create
to create the necessary
database support for session storage. You won’t have to
change anything about the way you actually use the session
object in your controllers. Rails will
automatically switch over to the database approach. The only change you
might notice is the removal of that 4K limit.
Even without the 4K limit, you’ll find that it’s much more efficient to store only minimal information in the session, preferably an identifier that can link to the necessary information in your application. It reduces the overhead for every request substantially.