#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' %>
show view code.
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 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" >>
User in the database still has the name ‘Eifion’.
Problems With Validations
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
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.