#13 Dangers of Model in Session
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.