"What users are online?" function

Ok, so you guys know the drill. I have users they log onto the site. I
need a way to determine which users are currently logged onto the site.
What is the “Rails” way to do this. I am willing to use whatever method
is considered best practice.

I have found this article:

http://matt-beedle.com/2006/12/13/rails-how-to-find-out-who-is-online/

This seems pretty straight forward. Is this essentially the accepted
standard for how to do this?

The gist of it is reading the information out of the sessions column. I
already use sessions and the active_record store so basically it just
means:

def who_is_online
@whos_online = Array.new()
onlines = CGI::Session::ActiveRecordStore::Session.find( :all,
:conditions => [ ‘updated_at > ?’, Time.now() - 10.minutes ] )
onlines.each do |online|
id = Marshal.load( Base64.decode64( online.data ) )
@whos_online << User.find(id)
end
return @whos_online
end

My question is how often is the “updated_at” field updated? Only when I
log in? Do I need to update the field myself before every controller
action? This method requires me to get a list of id’s and then go
through and do a query for the information of every single user.

Basically, I am just curious if this is the smart way and be done with
it or if I am missing something.

updated_at is updated every time you save your model.

because of the stateless nature of HTTP you cant know who is online. the
last request was made.

the simplest way to track if a user is logged in is to add a reoccurring
ajax call to your layouts. that way as long as their on your site your
app will be notified every x seconds. when this request comes in go into
the session and if there is user info there get the user object and save
it. now you can do something like

User.find(:conditions => [‘updated_at > ?’, 5.min.ago])

Note that this is very resource intensive and should be avoided if at
all possible.

It might even be worth while to create an active_users table that stores
user_id and an updated_at to track who did what how long ago which may
reduce the overhead though not by much. consider this a optimization to
try if you need it and not before

-K

On Jan 18, 2008, at 8:54 AM, Nathan E. wrote:

This seems pretty straight forward. Is this essentially the accepted
standard for how to do this?

That technique loops over the entire sessions table and unmarshalls
each record. That’s expensive.

These are alternative approaches. They assume it is enough for your
purposes to define “being logged in” as having done a request within
some time window:

  1. The users table has a timestamp column seen_at that is updated each
    time a user makes an authenticated request. There you count the number
    of users whose seen_at belongs to the time window in a single query.
    This approach works with any session storage.

  2. If you have a sessions table add to it a user_id column and
    maintain it with a callback in the Session model. There you count the
    distinct non NULL user_ids whose session updated_at belongs to the
    time window. That’s again a single query.

– fxn

Thanks for your input so far. If anyone has more suggestions keep them
coming but I am interested in your option 1 Xavier.

Essentially, add a “seen_at” field to user and then update it every time
they do something that requires being logged in. Then I could just
query:

User.find(:conditions => [‘seen_at > ?’, 5.min.ago])

and to update seen_at, I could simply add a before_filter in my
application controller that checks if there is a current user and if
there is simply update their ‘seen_at’ attribute.

Does that cover the basics of the idea? I think I like it. Would any
others agree this is a good approach or am I missing a major downside to
this method?

Xavier N. wrote:

  1. The users table has a timestamp column seen_at that is updated each
    time a user makes an authenticated request. There you count the number
    of users whose seen_at belongs to the time window in a single query.
    This approach works with any session storage.

– fxn

Thank you Xavier. I love this place. I can post a question and have it
answered well within a couple of hours.

I understand about the count being cheaper but in my particular case I
actually need to display a list of all users that are your friends and
who are also online like a buddy list so I think the expense of creating
the AR objects may be necessary.

Thanks again though.

On Jan 18, 2008, at 11:32 AM, Nathan E. wrote:

I understand about the count being cheaper but in my particular case I
actually need to display a list of all users that are your friends and
who are also online like a buddy list so I think the expense of
creating
the AR objects may be necessary.

Good, then the fine point is to scope the query directly to the user’s
friends. For example this way:

 class User < AR::Base
   has_many :friends, ...

   def online?
     seen_at && seen_at > 5.minutes.ago
   end

   def friends_online
     friends.select(&:online?)
   end
   ...
 end

– fxn

PS: That implementation is clean but fetches the entire collection of
friends. That could make sense depending on the application, otherwise
just fetch the online ones scoping with friends.find(…).

Xavier N. wrote:

   def online?
     seen_at && seen_at > 5.minutes.ago
   end

seen_at < 5.minutes.ago :wink:

Also, 5 minutes is a bit harsh of a definition of being online. You
might want to implement an away status too especially if youre going for
the buddy list feel.

def online?
seen_at && seen_at > 20.minutes.ago
end

def active?
seen_at && seen_at > 20.minutes.ago
end

def away

something in between

end

And don’t forget that when a user logs out (or their session gets
cleared because they spent too much time away from the app) you might
want to nullify seen_at as it would be redundant or at least kind of
pointless to store both seen_at and updated_at.

On Jan 18, 2008, at 10:32 AM, Nathan E. wrote:

Thanks for your input so far. If anyone has more suggestions keep them
coming but I am interested in your option 1 Xavier.

Essentially, add a “seen_at” field to user and then update it every
time
they do something that requires being logged in. Then I could just
query:

User.find(:conditions => [‘seen_at > ?’, 5.min.ago])

Sure you are aware of this, but just for the archives let me comment
that would instantiate all the records as AR objects, that’s very
expensive in general.

If you were just interested in the number of people online it is much
better to ask directly for the count:

User.count(:conditions => ['seen_at > ?', 5.minutes.ago])

With this solution you can also define a simple predicate

class User < AR::Base
  def online?
    seen_at && seen_at > 5.minutes.ago
  end
  ...
end

Etc. It works quite well.

and to update seen_at, I could simply add a before_filter in my
application controller that checks if there is a current user and if
there is simply update their ‘seen_at’ attribute.

Does that cover the basics of the idea?

That’s right.

– fxn

err:

def online?
seen_at && seen_at < 20.minutes.ago
end

def active?
online? && seen_at < 5.minutes.ago
end

I recently wrote a follow up to Matt B.'s blog on my own blog:
http://www.williambharding.com/blog/?p=99

A couple differences between his approach and mine:

  • I added fields to the session model itself to avoid the Marshalling/
    Unmarshalling to find a specific attribute within the session
  • He doesn’t discuss exactly how (or if) he goes about updating which
    users have been seen when. I discuss how I did it with a before
    filter that does one SQL Update statement, which is about as efficient
    as possible
  • My query for users online is scoped such that it only returns the
    fields I need (using a :select), and it limits the number of users
    returned

Generally speaking, I needed a solution that performs as well as
possible, since it will be called extremely often (both to update when
a user was seen and to find which users have been seen recently). I
think that the solution I came up with ought to fit the bill pretty
well, though I’d welcome any comments if others have tweaks to my
approach.

Bill

On Jan 18, 2008, at 6:11 PM, Glenn Gentzke wrote:

def online?
seen_at && seen_at < 20.minutes.ago
end

def active?
online? && seen_at < 5.minutes.ago
end

Oh, I thought because of the code that the first “sentence” was kind
of a joke, but looks like it was not :-).

If a user just did a request you certainly want to consider him to be
online, right?

 $ script/console
 Loading development environment (Rails 2.0.2)
 >> seen_at = Time.now
 => Fri Jan 18 18:25:30 +0100 2008
 >> seen_at > 5.minutes.ago
 => true

5.minutes.ago marks a point past in time. A user is online if it has
been seen recently, so seen_at has to be greater than what you
consider to be recent enough. Note that “>” is defined such that today

yesterday.

– fxn

I follow a similar approach myself though I generally leave the session
model alone.

May I suggest a possible improvement. It can reduce the SQL Update
frequency if you only update say
every 5 minutes. This can be done in the before_filter or in the SQL
statement, whichever you
prefer.

Cheers,

– Long
http://FATdrive.tv/wall/trakb/10-Long
http://FATdrive.tv/

----- Original Message -----
From: [email protected]
To: “Ruby on Rails: Talk” [email protected]
Sent: Monday, January 28, 2008 4:24 PM
Subject: [Bulk] [Rails] Re: “What users are online?” function

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs