The Web was built to do one thing at a time. Each request is, from the point of view of the client and server, completely independent of every other. A group of requests might all operate on the same database, and there can be clear paths from one part of an application to another, but for the most part, HTTP and scalable web application design both try to keep requests as independent as possible. It makes the underlying infrastructure easier.
Rails balances that simplicity of infrastructure with application developers’ need for a coherent way to maintain context. Rails supports several mechanisms keeping track of information about users. If you want to keep track of users manually, you can work with cookies. If you want to keep track of users for a brief series of interactions, Rails’ built-in session support should meet your needs.
If you want to keep track of users on a long-term basis, you’ll want to use the authentication tools covered in Chapter 14.
Like nearly every web framework, Rails provides support for cookies. Cookies are small pieces of text, usually less than 4 KB long, that servers can set on browsers and browsers will pass back with requests to servers. Browsers keep track of where cookies came from and only report cookies’ values to the server where they came from originally. JavaScript code can reach into a cookie from a web page, but Rails itself is more interested in setting and receiving cookies through the HTTP headers for each request and response.
When cookies first appeared, they were loved by developers who saw them as a way to keep track of which user was visiting their site, and hated by privacy advocates. Much of that uproar has calmed, because cookies have become a key part of functionality that users like, but there’s still potential for abuse.
To stay on the good side of potentially cranky users, it’s best to set cookie lifetimes to relatively brief periods and use longer cookies only when users request them (as in the classic “remember me” checkboxes for logins). Never store sensitive information directly in cookies, either!
In most cases, your application probably doesn’t need to access cookies directly. Rails’ built-in support for sessions and the tools for user authentication can both manage all of the overhead of keeping track of users for you. However, if you want to use cookies directly, either because you have specific needs for them or because you’re interacting with other code (say, a JavaScript library) that expects a particular cookie to provide it with a key value, then the demonstration below should give you a clear idea how it works. Figure 13-1 provides an overall picture of how cookies flow through an application.
Because cookies are about storing data on the client, not the
server, a really simple example will do. To get started, this example
will build on one of the simplest examples in this book so far, the
first version of the entry
controller
with its sign_in
method from Chapter 4. (Code for this
example is in ch13/guestbook011.)
If you’d rather create a new blank copy of this application, run
rails guestbook
, then cd guestbook
if necessary, and then finally
ruby script/generate controller
entry
.
Example 13-1 shows the new app/controllers/entry_controller.rb file, with changes from the Chapter 4 version in bold.
Example 13-1. Keeping track of names entered with a cookie
class EntryController < ApplicationController def sign_in@previous_name = cookies[:name]
@name = params[:visitorName]cookies[:name] = @name
end end
The new first line collects the previous name entered from the
cookie and stores it as @previous_name
so the view can display it.
(The cookie data comes to the server through the HTTP request headers.)
The second line, as before, gathers the new name from the :visitorName
field of the form, and the third
name stores that name (even if it’s empty) as a cookie that will be
transmitted to the browser through the HTTP response headers.
The view in app/views/entry/sign_in.html.erb just needs three extra lines to show the previous name if there was one, as shown in Example 13-2.
Example 13-2. Reporting a previous name 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 @previous_name != '' %>
<p>Hmmm... the last time you were here, you said you were <%=h @previous_name
%>.</p>
<% end %>
</body> </html>
This tests to see whether a previous name was set and, if so, presents the user with what they’d entered. All this really does is demonstrate that the cookie is keeping track of something entered in a past request, making it available to the current request.
The HTTP headers that carry the cookie back and forth are normally invisible, though not that interesting. You can see cookie information in most browsers through a preferences or info setting. At the beginning, this application looks much like its predecessor, as shown in Figure 13-2.
In Firefox 3.0, you call up the cookie inspection window at Tools/Page Info, then the Security tab, and then the View Cookies button halfway down the screen on the righthand side. You’ll see something like Figure 13-3.
For now, the :name
cookie is
the one that matters, and as you can see, its content is blank. It came
from localhost
, because this is a
test session on the local machine. The path is set to /
, the Rails default, making it accessible to
any page that comes from the localhost server. It gets sent with all
HTTP connections and will expire “at end of session”—as soon as the user
quits the browser. Users can, of course, delete the cookie immediately
with the Remove Cookie button.
If you enter a name, say, “Zimton,” and click the “Sign-in” button, you’ll see something like Figure 13-4.
Because the :name
cookie was
previously set to an empty string, the query message still isn’t shown,
but this time the trigger is set. If you inspect the cookie, you’ll see
that the :name
cookie’s value is now
“Zimton,” as shown in Figure 13-5.
If you enter a new name, say “Zimtonito,” and click the “Sign-in” button, the Rails application will get “Zimtonito” through the form, while still getting “Zimton” from the cookie. This time, it will ask why the name has changed, as shown in Figure 13-6.
Storing the name information in the cookie gives Rails a memory of what happened before and lets it notice a change.
If you choose to use cookies directly, rather than relying on
Rails’ other mechanisms for keeping track of interactions across
requests, there are a few more parameters you should know about when
setting cookies. If you set more than just a value for a cookie, the
syntax changes. To set both a value and a path for the :name
cookie, for example, you would
change:
cookies[:name] = @name
to:
cookies[:name] = { :value => @name, :path => '/entry' }
The available parameters include:
:value
The value for the cookie, usually a short string. (Typically this is a database key, but make sure not to store anything genuinely secret.)
:domain
The domain to which the cookie applies. This has to be a
domain that matches with the domain the application runs at. For
example, if an application was hosted at http://myapp.example.com,
:domain
could be set to http://myapp.example.com or
http://example.com. If it
was set to http://example.com, the cookie
could be read from http://myapp.example.com,
http://yourapp.example.com,
or http://anything.example.com.
:path
The path to which the cookie applies. Like :domain
, the :path
must be all or part of the path
from which the call is being made. From /entry/sign_in, it could be set to
/, to /entry, or to /entry/sign_in. The cookie can only be
read from URLs that could have set that path. (By default, this is
/
, making the cookie available
to everything at your
domain.)
:expires
The time at which the cookie will expire. The easiest way to
set this is with Ruby’s time methods, such as 5.minutes
or 12.hours.from_now
.
:secure
If set to true
, the
cookie is only reported or sent over secure HTTP (HTTPS) connections.
:http_only
If true
, the cookie
is transmitted over HTTP or HTTPS connections, but is not
available to JavaScript code on those pages.
Anytime you find yourself using cookies, especially if you’re doing complicated things with cookies, you should consider using sessions or authentication instead.