A typ­i­cal rails design pat­tern is to store object ids in ses­sion vari­ables rather than the objects them­selves. Objects can be unwieldy depend­ing on size, com­plex­ity, and asso­ci­a­tions. Ids on the the other hand are small, sleek, and more agile.

So to get an object you do the following.

instance = Object.find(session[:object_id])

Ses­sion data tends to be tem­po­rary. Now if the ses­sion vari­able is noth­ing but a ref­er­ence to an object that is per­sis­tent, say the cur­rent user, then delet­ing the ses­sion object is no big deal. How­ever, if the inten­tion is that the object the vari­able ref­er­ences is also tem­po­rary that could be a prob­lem. Now using the above code to obtain an instance, you can always call

instance.destroy

then just set your ses­sion vari­able to a new value, or nul­lify it (and automag­i­cally remove it from the ses­sion object).

Now here’s where it get’s tricky. What if the user agent does not call the action to man­u­ally destroy the instance? Maybe the per­son behind the browser had to go get a cup of cof­fee. Maybe the coder behind your app—wink, wink, nudge, nudge—didn’t put in the action to man­age tem­po­rary objects. In any case, the ses­sion will expire and your sched­uled cron job will clear the ses­sions duti­fully in the case of PStore or ActiveRe­cord­Store, or will expire out­right in the case of Mem­cached. Unfor­tu­nately, the object to which the ses­sion pointed will linger on (unless of course you add that code to the cron job as well, but that is arguably bad (see below)).

So, what’s the solu­tion? Thanks to a tip from Ruby­Pan­ther on #ruby­on­rails and a bit of Google search­ing, I man­aged to hack some­thing together.

First, setup Rails to store ses­sions in an ActiveRe­cord store. With that you’ll get a ses­sions table. Most of what Rails does with the ses­sions table is hid­den behind the scenes, and you want to keep it that way. This doesn’t mean we can’t stick our hand into the cookie jar. (OK that’s a bad pun.) So next let’s cre­ate a new model.

class Session < ActiveRecord::Base
  # borrowed from lib/action_controller/session/active_record_store.rb
  def marshal(data)   Base64.encode64(Marshal.dump(data)) if data end
  def unmarshal(data) Marshal.load(Base64.decode64(data)) if data end

  def before_destroy
  data = self.unmarshal(self.data)
    if data[:object] then
      object = Object.find(data[:object])
      object.destroy
    end
    true
  end
end

Because the ses­sions table is an ActiveRe­cord store, we can cre­ate an ActiveRe­cord model to get access to it. The mar­shalling func­tions are needed to access the ses­sion data which is noth­ing more than a hash. Then all we need to do is to define the before_destroy call­back to destroy the object that matches the id stored in data[:object]. Now with the fol­low­ing call

s = Session.find(1)
s.destroy

The ses­sion object as well as the object ref­er­enced by session[:object] will be destroyed.

So why the trou­ble of doing this as opposed to just man­ag­ing it in the script file called by the cron job? Well for one thing, your script becomes cleaner.

Sessions.find(:all, :conditions => ["updated_at <= ?", Time.now]).each { |session| 
  session.destroy 
}

Then there’s the argu­ment that how an object is destroyed should be defined in a model def­i­n­i­tion. Plus if the code can be improved to use intro­spec­tion to deter­mine which object to delete based on the hash key, then all the better.

What would be cooler? How about mak­ing a ses­sion object asso­cia­tive? Wouldn’t it be cool to do some­thing like

object.session.create # object.session_id belongs_to :sessions, :dependent => true

3 Responses to “Hey Session, Did You Forget to Delete Me?”

  1. stephan said on March 31st, 2008 at 2:10 am:

    Hi Brian,

    thanks for this arti­cle. I think it’s about the only one you can find on google. But i got one prob­lem with this solu­tion: the before_destroy isn’t called, when I run it as a script/runner (which i want to imple­ment as a cron job). When im delet­ing it man­u­ally from the con­sole every­things work­ing, and the linked object gets deleted too, but not, when run the script/runner. Then the ses­sions get destroyed, but the linked object stays…
    Do you know any solu­tion to this?

    stephan

  2. joe said on October 23rd, 2008 at 12:00 pm:

    Is it pos­si­ble to change the neon-death-ray blue text on gray back­ground for the code exam­ples? It makes me want to poke my eyes out.

  3. Brian said on October 23rd, 2008 at 3:33 pm:

    Yeah, I’m plan­ning on a new theme.