How can I extract the attributes from stored session "data" in the DB (using "active_record_store" for session storage)

Hi,

I’m using active_record_store for storing my session information. I can add custom attributes to the session and then access these for that user session within a controller say, however what I would like to do is iterate through ALL session data whilst iterating access the attributes in the session data storage.

That is in the database “sessions” table there seems to be a “data” column for which rails stores all the session attributes. I’m trying to work out how to access the attributes in this data?

Is there a way to do this? I haven’t found a session class method that assists yet.

Background: I’m just wanting to store the UserId in the session which is then persisted to the database with the session -> can then iterate through all active sessions to see who is logged in.

Thanks

PS. The other aspect of my question is to decide whether I need to add an explicit column in my sessions table for user_id, so that the user_id is held in it’s own table as opposed to serialized within the rails session data. That is if I can’t work out how to access it from the session data then I’ll have to put it into a special column (an overhead I know) so that it gives me the means of accessing the user id information (when producing a list of active users).

PSS. Just tried using Marshal.load() but this didn’t work. Any suggestions/ideas ???

Marshal.load(<contents of "data" field obtained for a session record in the database>)

TypeError: incompatible marshal file format (can't be read)
       format version 4.8 required; 66.65 given

arrr, think I got it via:

    allsessions = Online.find(:all)
    allsessions.each do |s|
       dataHash = CGI::Session::ActiveRecordStore::Session.unmarshal

(s[:data])
userId = dataHash[:user_login]
puts “User Login[#{s.id}] = #{userId}”
end

Hi~

arrr, think I got it via:

        allsessions = Online.find(:all)
        allsessions.each do |s|
           dataHash = CGI::Session::ActiveRecordStore::Session.unmarshal (s[:data])
           userId = dataHash[:user_login]
           puts "User Login[#{s.id}] = #{userId}"
        end

  Save yourself some trouble and **DO NOT DO THAT*** . That will be such a performance killer that it will never be able to hold up when you get a good number of sessions active. That code will loop over every single session and unmarshall it to search it for the user_id. In even a small rails app that hasn't cleaned out the session recently that can be thousands and thousands of sessions. Looping over all these sessions is going to kill your server.

  You are much better off altering the actual sessions table and adding a user_id column that can get set in an after or before filter somewhere. Seriously, you will save yourself a lot of hassle if you avoid the loop over all sessions and unmarshal approach Greg.

Cheers-

-- Ezra Zygmuntowicz-- Lead Rails Evangelist
-- ez@engineyard.com
-- Engine Yard, Serious Rails Hosting
-- (866) 518-YARD (9273)

[SNIP]

         You are much better off altering the actual sessions table and
adding a user_id column that can get set in an after or before filter
somewhere. Seriously, you will save yourself a lot of hassle if you
avoid the loop over all sessions and unmarshal approach Greg.

While I'm sure Ezra's right it does bring up a couple other issues.
1) "In even a small rails app that hasn't cleaned out the session
recently"

That should NEVER be the case. Yes it is by default, but you should be
taking steps to avoid that. Yes, you can have a cron job run but
personally I hate the idea of having such an important part of my
system be dependant upon a completely separate mechanism. Makes it
harder to port between os's, some isps don't give you access to your
crontab, etc. My solution is to just hook the login process. Every
time someone logs in have it finish off with a call to the db to
delete old sessions.

2) The assumption that because there's a record in the session table
the person is "logged in". You've GOT to be doing some checking to
see WHEN that session record was created or last touched. The problem
is that you can't wipe out sessions to early or you'll annoy your
users. So you have to leave them there for a while. If you leave them
for only half an hour (not recommended) you'll still be listing a
bunch of users as "logged in" who haven't touched your system for
nearly 30 minutes.

3) For most sites this isn't an issue but.... Should you be so
fortunate as to write the next basecamp you probably don't want to be
doing a database write for every single page load. Reads are quick and
easy on the db writes are slower and can require it to reindex things.
So my previous note about checking when it was last updated can become
problematic because it requires constantly writing to that table. Then
again, maybe this last one is just me being overly cautious.

- kate = masukomi

Hi Ezra/Kate - thanks for the advice. I have been wondering which way to go here. My thinking was as follows:

Assumptions

  • Inactive sessions are cleaned regularly (via some approach)

Option 1 - Add specific UserId column to sessions table

  • CON - Need to perform an database update to put the userId in place, each request (unless there’s a way in one’s before_filter to tell it’s a request associated with the creation of the session for the first time I guess)

  • CON - Some minimal extra complexity in the code (i.e. for each request what has to be done)

  • ADV - Perhaps faster when do you want to pull back a list of active users

Option 2 - Use marshalled session data (stored in the data column in session table)

  • ADV - Can easily add userID to the session when performing normal authentication/authorisation checks, so no additional overhead (
    e.g. find session via AR then update)
  • CON - Perhaps slower to iterate through list, BUT the only time one would need to do this would be for someone requesting a list of active users logged on.

I kinda thought Option 2 may be better as it avoids an extract database hit per request, in return for a slower “get me active sessions” call which is only occasionally when requested by a user.

Comments?

Hi~

Hi Ezra/Kate - thanks for the advice. I have been wondering which
way to go here. My thinking was as follows:

Assumptions
* Inactive sessions are cleaned regularly (via some approach)

Option 1 - Add specific UserId column to sessions table
* CON - Need to perform an database update to put the userId in
place, each request (unless there's a way in one's before_filter to
tell it's a request associated with the creation of the session for
the first time I guess)
* CON - Some minimal extra complexity in the code (i.e. for each
request what has to be done)
* ADV - Perhaps faster when do you want to pull back a list of
active users

Right but keep in mind that the session gets read from and then
written to the db on every request anyways. So the session is already
going to be read from the db at the beginning of each request. If you
set the user_id column of the session object in a before filter it is
no extra db overhead because the session was already read from the
db. Then at the end of the request, the session has to be written
back out to the db anyways so setting the user_id field when this
happens does not create extra db queries if done right.

Option 2 - Use marshalled session data (stored in the data column
in session table)
* ADV - Can easily add userID to the session when performing normal
authentication/authorisation checks, so no additional overhead
( e.g. find session via AR then update)
* CON - Perhaps slower to iterate through list, BUT the only time
one would need to do this would be for someone requesting a list of
active users logged on.

The real problem with this way of doing things is memory and cpu
time. Lets say you keep your sessions cleaned out pretty regularly.
So assume 200 sessions laying around at any one time(rails makes a
lot of sessions entries so this number is conservative). So each of
these 200 sessions now need to be loaded into memory and unmarshaled
to see the user_id inside the session. That means 200 times however
much data is in each of those sessions. This will end up making your
app leak memory or at least taking a lot more then it needs. Plus it
will burn your cpu while it unmarshalls and un base64 encodes all
that session data.

I kinda thought Option 2 may be better as it avoids an extract
database hit per request, in return for a slower "get me active
sessions" call which is only occasionally when requested by a user.

I think that you can add a user_id column to the sessions model and
not incur anymore database queries then the session is already doing
without it. Its just a matter of writing and reading the user_id from
the Session model instead of as a hash in its data part.

Cheers-
-Ezra

Comments?

[SNIP]

> You are much better off altering the actual sessions
table and
> adding a user_id column that can get set in an after or before
filter
> somewhere. Seriously, you will save yourself a lot of hassle if you
> avoid the loop over all sessions and unmarshal approach Greg.
>

While I'm sure Ezra's right it does bring up a couple other issues.
1) "In even a small rails app that hasn't cleaned out the session
recently"

That should NEVER be the case. Yes it is by default, but you should be
taking steps to avoid that. Yes, you can have a cron job run but
personally I hate the idea of having such an important part of my
system be dependant upon a completely separate mechanism. Makes it
harder to port between os's, some isps don't give you access to your
crontab, etc. My solution is to just hook the login process. Every
time someone logs in have it finish off with a call to the db to
delete old sessions.

2) The assumption that because there's a record in the session table
the person is "logged in". You've GOT to be doing some checking to
see WHEN that session record was created or last touched. The problem
is that you can't wipe out sessions to early or you'll annoy your
users. So you have to leave them there for a while. If you leave them
for only half an hour (not recommended) you'll still be listing a
bunch of users as "logged in" who haven't touched your system for
nearly 30 minutes.

3) For most sites this isn't an issue but.... Should you be so
fortunate as to write the next basecamp you probably don't want to be
doing a database write for every single page load. Reads are quick and
easy on the db writes are slower and can require it to reindex things.
So my previous note about checking when it was last updated can become
problematic because it requires constantly writing to that table. Then
again, maybe this last one is just me being overly cautious.

- kate = masukomi

>

-- Ezra Zygmuntowicz
-- Lead Rails Evangelist
-- ez@engineyard.com
-- Engine Yard, Serious Rails Hosting
-- (866) 518-YARD (9273)

Thanks Ezra - this is helping me - one last clarification hopefully:

When you say “Then at the end of the request, the session has to be written back out to the db anyways so setting the user_id field when this happens does not create extra db queries if done right” , what did you mean by “if done right”?

Do I really need to work out how to integrate the writing of the additional “user_id” column into the rails session infra-structure to do this? Or can I do the simple concept of creating a model that happens to hook into the “sessions” table, and use this model as a means to update an existing session record with the “user_id”?

If the latter of the above two is ok (noting it simpler for me) can you explain how rails ensures this is not an extra database hit? Does activeRecord bundle all the requests up until the commit and then issue them to the database is some optimized form perhaps?

Tks again

Checkout http://habtm.com/articles/2005/12/15/whos-online for a way of adding data to the session table by creating a new model that uses the same table, creating new columns in the session table and then saving data in those columns for later direct access.

David

Greg Hauptmann wrote:

Hi~

Thanks Ezra - this is helping me - one last clarification hopefully:

When you say "Then at the end of the request, the session has to be written back out to the db anyways so setting the user_id field when this happens does not create extra db queries if done right" , what did you mean by "if done right"?

Do I really need to work out how to integrate the writing of the additional "user_id" column into the rails session infra-structure to do this? Or can I do the simple concept of creating a model that happens to hook into the "sessions" table, and use this model as a means to update an existing session record with the "user_id"?

If the latter of the above two is ok (noting it simpler for me) can you explain how rails ensures this is not an extra database hit? Does activeRecord bundle all the requests up until the commit and then issue them to the database is some optimized form perhaps?

Tks again

  Ok so when you use ActiveRecord sessions there is already a Session model hidden away in rails. The way to access it is to use the model method on the session object. So assume you have a user_id column added to the normal session table. When you want to store the user_id you can do this:

session.model.user_id=42

session.model returns a handle on the Session ActiveRecord object. So you can now use session.model_user.id to retrieve the user_id when you search for who is online

  Since the session is read from the db at the beginning of each request and then written back out at the end of each request the user_id will be saved automatically for you at the same time it saves the data blob into the session table. Hence the no extra db queries to do it this way.
.
Cheers-
-- Ezra Zygmuntowicz-- Lead Rails Evangelist
-- ez@engineyard.com
-- Engine Yard, Serious Rails Hosting
-- (866) 518-YARD (9273)

arrr, thanks Ezra…the penny just dropped for me :slight_smile: I’ll move forward on this basis.

Cheers
Greg

oh Ezra, where do you recommend that the user_id is added to the session? i.e. in the first before_filter
in the application.rb? would this be sufficient?

Greg-

oh Ezra, where do you recommend that the user_id is added to the session? i.e. in the first before_filter in the application.rb? would this be sufficient?

On 11/12/06, Greg Hauptmann < greg.hauptmann.ruby@gmail.com> wrote:arrr, thanks Ezra.....the penny just dropped for me :slight_smile: I'll move forward on this basis.

Cheers
Greg

  I would set the user_id in the same place you would have done it if you had stored it in the normal sessions hash. So yes a before filter in application controller works fine. You can always do a skip_before_filter in other controllers that don't need to set the user_id. So when you log in a user and you would normally be storing the user_id right in the normal session hash, you instead assign it to session.model.user_id

Cheers-
-- Ezra Zygmuntowicz-- Lead Rails Evangelist
-- ez@engineyard.com
-- Engine Yard, Serious Rails Hosting
-- (866) 518-YARD (9273)

ok - thanks