Hey Session, Did You Forget to Delete Me?
Posted September 21st, 2006 in ProgrammingTags: Programming, Ruby on Rails
A typical rails design pattern is to store object ids in session variables rather than the objects themselves. Objects can be unwieldy depending on size, complexity, and associations. 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])
Session data tends to be temporary. Now if the session variable is nothing but a reference to an object that is persistent, say the current user, then deleting the session object is no big deal. However, if the intention is that the object the variable references is also temporary that could be a problem. Now using the above code to obtain an instance, you can always call
instance.destroy
then just set your session variable to a new value, or nullify it (and automagically remove it from the session object).
Now here’s where it get’s tricky. What if the user agent does not call the action to manually destroy the instance? Maybe the person behind the browser had to go get a cup of coffee. Maybe the coder behind your app—wink, wink, nudge, nudge—didn’t put in the action to manage temporary objects. In any case, the session will expire and your scheduled cron job will clear the sessions dutifully in the case of PStore or ActiveRecordStore, or will expire outright in the case of Memcached. Unfortunately, the object to which the session 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 solution? Thanks to a tip from RubyPanther on #rubyonrails and a bit of Google searching, I managed to hack something together.
First, setup Rails to store sessions in an ActiveRecord store. With that you’ll get a sessions table. Most of what Rails does with the sessions table is hidden 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 create 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 sessions table is an ActiveRecord store, we can create an ActiveRecord model to get access to it. The marshalling functions are needed to access the session data which is nothing more than a hash. Then all we need to do is to define the before_destroy
callback to destroy the object that matches the id stored in data[:object]
. Now with the following call
s = Session.find(1) s.destroy
The session object as well as the object referenced by session[:object]
will be destroyed.
So why the trouble of doing this as opposed to just managing 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 argument that how an object is destroyed should be defined in a model definition. Plus if the code can be improved to use introspection to determine which object to delete based on the hash key, then all the better.
What would be cooler? How about making a session object associative? Wouldn’t it be cool to do something like
object.session.create # object.session_id belongs_to :sessions, :dependent => true
Hi Brian,
thanks for this article. I think it’s about the only one you can find on google. But i got one problem with this solution: the before_destroy isn’t called, when I run it as a script/runner (which i want to implement as a cron job). When im deleting it manually from the console everythings working, and the linked object gets deleted too, but not, when run the script/runner. Then the sessions get destroyed, but the linked object stays…
Do you know any solution to this?
stephan
Is it possible to change the neon-death-ray blue text on gray background for the code examples? It makes me want to poke my eyes out.
Yeah, I’m planning on a new theme.