Sessions N Such

Tháng Hai 17, 2009

Source: http://errtheblog.com/posts/22-sessions-n-such

Sessions in Rails are not black and white. Please stop treating them as such. This sort of thinking is deprecated and will be removed in 1.2.

Most of you know better, I’m sure, but humor me: there is a meme going around whereby sessions are thought to fill one of two roles in your Rails app: on or off. This is not the case. Sessions are wonderfully flexible and incredibly deep.

Real quick, there’s a really long paper about sessions on the web you can read if you want to know some non-Rails, general overview type stuff. Basically a session is two things: a cookie with a unique ID and some sort of data persisted by the app server and referenced by the cookie’s unique ID. As long as you’ve got that same ID in your temporary cookie, you’re in the same session and the app knows who you are. When you close your browser (or enough time passes), that temporary cookie goes away and you’re in a new session. That’s that. Here’s a really basic intro to using sessions in Rails if you’re unfamiliar.

Agile Sessions

Let’s dive right in. So once you disable sessions they’re off for good? Not true. You can disable and re-enable sessions all over the place. But why?

Well, maybe you don’t need sessions. Maybe you’ve got it all figured out with cookies and memcached on your big million dollar site, and sessions just weren’t needed. So you turn them off:

class ApplicationController < ActionController::Base
  session :off
end

First, this isn’t fullproof. Any routing errors bypass ApplicationController and will still create sessions. To really turn off sessions you need a catch-all route, which we won’t go into here. The curious can check out this trac ticket to get all the details.

Back to your million dollar app, where you’ve successfully semi-disabled sessions completely. You go live and suddenly you’re getting all kinds of comment spam. Hastily you scaffold an admin backend to the site so you can CRUD around with comments. flash and other sessiony goodness is required. What to do?

There’s no session :on, but you can un-disable (!) sessions just for that secret part of your site:

class Admin::Comments < ApplicationController
  session :disabled => false
end

That’s great, and efficient. No session overhead where it isn’t needed.

Quickly you decide you want to be ultra efficient. You only need sessions on four admin methods: delete, edit, create, and save. You try this, but it doesn’t work:

class Admin::Comments < ApplicationController
  session :off, :except => %w[delete edit create save]
end

The sessions are already turned off everywhere because of ApplicationController. What you need to do is turn them on for these specific methods:

class Admin::Comments < ApplicationController
  session :disabled => false, :only => %w[delete edit create save]
end

Turn them on by un-disabling sessions only for your special methods. Makes sense, right?

You don’t have to set session :off in ApplicationController, of course. You can keep sessions on throughout your whole app and only turn them off where they’re not needed by using :only and :except liberally:

class StoriesController < ApplicationController
  session :off, :only => %w[rss atom]
end

Just keep in mind who inherits from whom and you’ll be okay.

Like before_filter and friends, you can also pass a proc to :if to control whether sessions are enabled or disabled:

class StoriesController < ApplicationController
  session :off, :if => proc { |request| request.server_name == 'localhost' }
end

Something like that. Your proc is passed the request object we see all over the place. Plan accordingly.

There’s more to it than that, I’m afraid. Your big app is getting hundreds of hits and suddenly you realize it’s slowing down. Disk space is waning. Sessions are to blame. But there’s a solution.

ActiveRecordStore

The default storage for sessions in Rails is what’s known as PStore. Think of your Rails app as a bicycle and PStore as training wheels. Training wheels are really helpful and easy, but you’ll never get a date using them in public. Same goes for PStore.

There are better session storage options in Rails, for a number of reasons. Files stored locally on a server’s disk, for instance, are the definition of “doesn’t scale.” What happens when you add another server? I don’t even know. PStore is also pretty slow, according to the Internet.

One viable option is ActiveRecordStore. This comes with Rails and you can start using it right now.

Do this: rake db:sessions:create

This’ll give you a new migration for creating a sessions table ActiveRecordStore will be happy with. So, migrate.

Next go into environment.rb and uncomment this line:

config.action_controller.session_store = :active_record_store

You see, it’s basically done for you. Why even bother with file sessions? Get rid of them right away. This is share nothing at its finest, people.

You can easily clear out your sessions table during development, too:

rake db:sessions:clear.

If you’re feeling like a spelunker you can check out lib/action_controller/session/active_record_store.rb in ActionPack. Not only does that file house the ActiveRecordStore code, but also skeleton code upon which you may build your own DB session stores (thanks, core!).

SQLSessionStore

Speaking of custom DB session stores, the all-knowing Stefan Kaes has posted extensively on sessions. He’s got a SQLSessionStore that, he claims, will knock your socks off (if you’re on MySQL). Download the tarball, extract it into RAILS_ROOT/lib/, then add this code to the bottom of your environment.rb file (after commenting out the other session_store line higher up):

require 'sql_session_store'
require 'mysql_session'
ActionController::Base.session_options[:database_manager] = SQLSessionStore
SQLSessionStore.session_class = MysqlSession

Unfortunately for us, Mr. Kaes’ code can not use the database table generated by db:sessions:create without modification. You need to use this migration file to create the correct session table. But it’ll be worth it. Bare bones SQL, no AR overhead. Hot metal on metal action.

An important thing to remember when using a database session store is you need to clear out the session table. This won’t happen automatically, you know? Luckily both Stefan and Rails’ solutions provide a created_at column. With the ActiveRecordStore it’s as easy as writing a cron job.

memcached

The ol’ chestnut, memcached. Getting this session store to work is less straight forward: you need to install memcached and its dependencies first. Topfunky has a good memcached basics article which should help get you going. Namely, check out his sh script.

Once you have memcache-client and memcached itself up and running, the session store part is just a bit of code:

config.action_controller.session_store = :mem_cache_store

Make sure you setup the CACHE constant and memcache-client in your environment.rb file as per topfunky’s article. Something like this:

require 'memcache'
memcache_options = {
  :c_threshold => 10_000,
  :compression => true,
  :debug => false,
  :namespace => :app-#{RAILS_ENV}",
  :readonly => false,
  :urlencode => false
}

CACHE = MemCache.new memcache_options
CACHE.servers = 'localhost:11211'

ActionController::Base.session_options[:expires] = 1800
ActionController::Base.session_options[:cache] = CACHE

Memcached is nice because it has its own expiration system. We don’t have to worry about expiring sessions, it will take care of that as long as we set a timeout.

DRbStore

Uh, does anyone use this? I’m not sure they do. Seriously, Google is convinced I’m looking for some kind of Rails drugstore. It’s fun, though. Here’s how you do it.

Open environment.rb and uncomment the session_store line. Change it thus:

config.action_controller.session_store = :drb_store

Now I couldn’t really figure out how to start the server cleanly. What I had to do was copy actionpack/lib/action_controller/session/drb_server.rb to my RAILS_ROOT/script directory, then start it:

ruby script/drb_server.rb.

Black screen, but that’s okay. It seems to work just fine and is very basic. No real options or anything. No word on session expirations, for instance. Maybe something cool will come out of this one day. I imagine it’s possible to have some sort of distribute system setup ala memcached, but I also imagine memcached will always be faster.

Memory Store

Not memcached and not DRbStore, there’s also Memory Store. The simplest:

config.action_controller.session_store = :memory_store

Nothing else to it. Fast. Uses RAM. Expiration? Who needs it! And like PStore, it doesn’t scale.

Finally

There’s still a lot more to be said about sessions. The Rails wiki is just overflowing. A huge page on session options alone. I hope you’re up for it. Just be sure to watch out for gotchas when using sessions promiscuously, but don’t let that stop you.

Memory is what makes us who we are.