Close and Go BackBack to Viget

Wrapping Rails Session Hash

Matt Swasey
Matt Swasey, Former Staffer, September 16, 2009

The best way to use session data in Rails is sometimes a bit elusive. One technique I see from time to time is using a before_filter to pull data from the session, and store it in an instance variable.

class AccountsController < ApplicationController
  before_filter :find_account
  
  ...
  
  def find_account
    @account = Account.find(session[:account_id])
  end
end

The problem here is that the more places you start using the @account instance variable, the more confusing it gets. Suppose we wanted to reuse this before_filter, and so we move find_account to our ApplicationController. This would further obscure the location where the instance variable is being set.

Another drawback to using a before_filter is having to tediously define where you want the filter to run or not-to-run (using skip_before_filter). It would be nice if we could have that data only where we really needed it without worrying about declaring which actions have access to session data and which don't.

A Better Approach

A pattern I like to use when working with sessions in rails is wrapping session variables in getter and setter current_object methods. I picked this up a while ago while using RestfulAuthentication, I've found since that I use it in almost every project. Observe...

class ApplicationController < ActionController::Base
  helper_method :current_account
  
  def current_account
    @current_account ||= Account.find_by_id(session[:current_account_id])
  end

  def current_account=(account)
    session[:current_account_id] = account.try(:id)
  end
end

I am using find_by_id(id) because it returns a nil value when the record is not found rather than an exception, which is what is returned from an unsuccessful find(id). Keep this in mind, as you may prefer to throw an exception over a silent failure.

Using the conditional assignment method in our getter allows us to lazily load database records. It's also worth noting that the current_account method always returns a model instance, we only store the record's id in the actual session. This keeps our session data as small as possible.

I've declared these methods as helper methods inside the ApplicationController so that I can use them in my controllers and views alike. I find a call to 'current_account' in the view is easier to understand than an instance variable that is set somewhere other than the view's corresponding controller action.

Notice the use of Object#try when calling id on the account argument passed to current_account=. Using Object#try in this situation allows us to set session[:current_account_id] to nil using the same current_account= setter method. This is often seen in logout actions:

class SessionsController < ApplicationController
  ...
  def destroy
    self.current_account = nil
    redirect_to root_path
  end
end

A Gotcha

Remember to prepend the setter method with 'self' when using it in your controllers, else Ruby will make a local variable assignment to 'current_account' the variable, which is different from 'current_account=' the method. Obvious, perhaps, but it trips me up from time to time.

class AccountsController
  def create
    self.current_account = Account.find(params[:id])
    ...
  end
  ...
end

If anyone has other nice ways of handling session variables in Rails, let me know in the comments!

blog comments powered by Disqus

We're the Developers

at Viget Labs. We write about web development trends, tips, best practices, industry events, and our projects — all with an emphasis on Ruby on Rails.

Contact Us

Have any questions, comments, ideas, or secrets to share? Let us know.


What color is the sky?

Sorry, you need to have Javascript enabled to use this form. (Don't blame us, blame the spammers!) If you'd like to contact us, please visit our Contact page.