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.
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.
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.
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'
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, so that it can still be called (you can’t use
superwhen monkey patching).
- Define a new
- In the new
set_sessionmethod 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_sessionmethod to the current object, call it, and return the Session ID
- Make the new
set_sessionmethod private, this isn’t required, but the original
set_sessionmethod 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
Every time I write ‘Conditionally Disabling Database Sessions in Ruby on Rails 3’ I can’t help but think of this:
- Matt Bearman