homeASCIIcasts

13: Dangers of Model in Session 

(view original Railscast)

Other translations: It Es Fr

In this episode we’ll show you why it’s a bad idea to store a model in the session.

class UserController < ApplicationController
  def prepare
    session[:user] = User.find(:first)
    redirect_to :action => :'show'
  end
  def show
    @user = session[:user]
  end
  def update
    @user = session[:user]
    @user.name = 'Foo'
    redirect_to :action => 'show'
  end  
end

Controller with actions that store a model in the session.

In the code above we have a controller with three actions. The first action, prepare, finds the first User, stores it in the session, then redirects to the show action. The show action gets the current user from the session and outputs some debug information about that user.

<%= debug(@user) %>
<%= link_to 'update', :action => 'update' %>

The show view code.

The show view has a link to the update action. The update action gets the current user from the session again, changes one of its properties then redirects back to show. The change made in update should be temporary as it’s not written back to the database. Finally, update redirects back to show so that we can see the properties of the user again.

We first go to /user/prepare which puts the first user into the session and redirects to show. In show we can see that the name is correct. We then click ‘Update’, the name property for the user is changed and we’re redirected to show again. The name is now ‘Foo’, which is different from the name for that user in the database. Because we’ve stored the user in the session, any changes made to it are persisted across requests so every time we get the user back from the session we’re seeing the modified User rather than the one stored in the database. This can lead to some tricky bugs.

NooNoo:ep13 eifion$ script/console
Loading development environment (Rails 2.2.2)
>> User.first.name
=> "Eifion" 
>>   

The User in the database still has the name ‘Eifion’.

Problems With Validations

Our User model has a validation that checks that the name exists (with validates_presence_of). We’ll update the update action to set the name to an empty string, check that the user is valid and see what happens.

def update
  @user = session[:user]
  @user.name = ''
  @user.valid?
  redirect_to :action => 'show'
end    

The updated update action.

Now we’ve set the name to be an empty string, but there are still errors. Once we’ve redirected to another page the errors shouldn’t be persisted but, because we’re storing the model in the session, they are, even after redirecting. This could cause confusion to a user who might see validation errors on a page and find them still there when they return to the page later.

The Correct Way To Do It

Now we’ll do it correctly. Instead of storing the User model in the session, we’ll store the user’s id. The updated controller code will look like this:

class UserController < ApplicationController
  def prepare
    session[:user_id] = User.find(:first).id
    redirect_to :action => :'show'
  end
  def show
    @user = User.find(session[:user_id])
  end
  def update
    @user = User.find(session[:user_id])
    @user.name = 'Foo'
    redirect_to :action => 'show'
  end  
end

Controller with actions that store an id in the session.

Now, only the id is stored in the session and the user is fetched from the database for each request. This time the change made to the user’s name is not persisted and remains the same across requests.

Hopefully this episode has shown the dangers of storing models and other complex objects in Rails’ session. The session object should only be used to store simple objects such as array, strings and integers.