Conditionally Disabling Database Sessions in Ruby on Rails 3
I’ll start with the obvious: Why would you want to conditionally disable database sessions in Ruby on Rails 3? It’s a problem I’ve faced with BugMuncher for a long time, but for a different reason than most. The most common reason people want this is to stop sessions being created for web crawlers (bots). Here’s a couple of people having this issue - this person and this one too.
The first link covers disabling Cookie based sessions for bots, but for those using Database storage it doesn’t work.
TL;DR
Not interested in the how or why? Simply create a file config/initializers/session_store_ext.rb
and fill it with the following. Also make sure to define BOT_REGEX to match known crawler user agents.
The gory details
As I mentioned earlier, the issue with BugMuncher wasn’t crawlers, as the BugMuncher app disallows crawlers through its robots.txt, and even if it didn’t, the bots would only ever see a login page.
The issue I was having with BugMuncher was actually much worse - The BugMuncher feedback tab is embedded in hundreds of sites, and any time someone saw the BugMuncher feedback tool on any website, a completely unneeded session was created in BugMuncher’s database. Even with a script to clean out old sessions, there were millions of rows in the sessions table, and it was steadily growing. Something had to be done.
After a couple of hours of Googling, and only finding solutions that covered cookie storage, I realised I’d have to fix this one myself.
Finding the session storage code
I spent some time digging through the Rails source code, but at this point I wasn’t too sure what I was looking for, so I decided to take a more direct approach: On my local machine I deleted the sessions table and fired up my local copy of BugMuncher, knowing it would throw an error when it couldn’t find the sessions table. This gave me a stack trace which I hoped would guide me to the file I needed to modify.
One line in the stack trace instantly stood out:
activerecord (3.2.8) lib/active_record/session_store.rb:307:in `get_session'
The get_session
method was probably not going to be exactly what I was looking for, as the loading of sessions wasn’t the problem, I needed to find where they were saved. It was very close though, as a set_session
method is defined directly below get_session
, and that was the method I needed to overwrite.
BugMuncher uses a slightly different monkey patch to the one above, as I needed to disable sessions based on the current controller, instead of the user agent. This is only a slight (1 line) difference, and both patches do the following:
- Store the original
set_session
method as_set_session
, so that it can still be called (you can’t usesuper
when monkey patching). - Define a new
set_session
method. - In the new
set_session
method check to see if the request is from a bot (or in BugMuncher’s case, a specific controller). - If so, just return the Session ID (what the original method returns) without saving the session.
- Otherwise bind the currently unbound original
_set_session
method to the current object, call it, and return the Session ID - Make the new
set_session
method private, this isn’t required, but the originalset_session
method is private, and I like to respect that.
That’s the story of my first Rails Monkey Patch, and one that has since slowed down the growth of BugMuncher’s sessions table by a factor of around 14,000
P.S.
Every time I write ‘Conditionally Disabling Database Sessions in Ruby on Rails 3’ I can’t help but think of this:
- Matt Bearman