Skip to content
This repository has been archived by the owner on Oct 19, 2018. It is now read-only.

How to track if user is active

Jan Biedermann edited this page Jan 29, 2018 · 2 revisions

written by @mareczek

Is this user active or not?

Hi! So, Hyperloop is very connected - the backend data models connect to the UI automagically. The data arrives by just writing User.find(123).name, and it gets updated automagically as well.

But, how can we tell if a User is currently signed in or not? We have not found any built in method that would have an answer for this question. Hence, this short how to (our specific scenario).

Disclaimer

This is not a offical howto :-) It's just how we (EngineArch) implemented this in a project where we use Hyperloop (which we love). We are very open for suggestions!

Steps

Database migration

We decided to cache this information on the User model (in the db), hence a migration file. We would like to know:

  1. since when is the user active (signed in)
  2. or since when is the user inactive

Having a User, we add two datetime attributes:

bundle exec rails generate migration add_session_timestamps_to_user active_since:datetime inactive_since:datetime

you should now have a migration file generated in <app root>/db/migrations/<timestamp>_add_session_timestamps_to_user.rb with the contents:

class AddSessionTimestampsToUser < ActiveRecord::Migration[5.1]
  def change
    add_column :users, :active_since, :datetime
    add_column :users, :inactive_since, :datetime
  end
end

We do not intend on searching the DB based on these attributes, hence no index applied. If in your use-case you will be filtering the DB based on these attributes do not hesitate to add a index. Run the migration:

bundle exec rails db migrate

Monkey patching hyperloop_connection.rb

In this step we will hook into the lifecycle of Hyperloop::Connection which is an ActiveRecord object, with standard Rails callbacks. We rely on Hyperloop to manage these entities.

Navigate to <app root>/config/initializers and create a file called hyperloop_connection.rb. Copy-paste the below content and save the file:

module Hyperloop
	class Connection < ActiveRecord::Base
		unless RUBY_ENGINE == 'opal'

			after_create 	:log_active_since_on_user, 		if: :user_specific_channel?
			after_destroy 	:log_inactive_since_on_user, 	if: :user_specific_channel?

			def user_specific_channel?
				channel =~ /User-[0-9]+/
			end

			def user
				channel =~ /User-([0-9]+)/ ? @user ||= User.find_by_id($1) : nil
			end

			def log_active_since_on_user
				puts "Hyperloop::Connection: WILL log_active_since_on_user, #{user.try(:id)}"
				user.try(:update_attributes, { active_since: Time.now, inactive_since: nil })
			end

			def log_inactive_since_on_user
				puts "Hyperloop::Connection: WILL log_inactive_since_on_user, #{user.try(:id)}"
				user.try(:update_attributes, { active_since: nil, inactive_since: Time.now })
			end

		end
	end
end

The above code will run only on the server side of you application thanks to

unless RUBY_ENGINE == 'opal'

Note: the lines with puts are optional - we use them for debugging

Maintenace before the server starts

Whenever your app server is down all of the connections within you app are obviously lost. Hence, whenever the server (production, staging or any other env) starts it is a good idea to reset all active_since. We will not touch the inactive_since attribute since we will never be certain when the server was turned off or crashed.

Navigate to <app root>/config/initializers and create a file called user_session_timestamp_reset.rb. Copy-paste the blow content and save the file.

if Hyperloop.on_server?
    user_model = begin
      Object.const_get 'User'
    rescue LoadError
    rescue NameError => e
		puts "Model `User` does not exist, hence will not reset session timestamps"
    end

	if user_model
		if (missing_columns = ['active_since', 'inactive_since'] - User.column_names).empty?
			result = User.update_all(active_since: nil)
			puts "Updated all User records with `active_since: nil` -> #{result}"
		else
			puts "`User` table does not have columns: #{missing_columns.join(', ')}. ...add them :)"
		end
	end
end

End.

Final result

If everything went smooth you are now able to see the timestamps on you User instances. We use these attributes to represent the session state of a user with a Hyperloop::Component:

class UserStatus < Hyperloop::Component
  param active_since:   nil
  param inactive_since: nil

  def render
    div(class: "person-status #{calculate_status}")
  end

  def calculate_status
    if params.active_since
      'online'
    elsif params.inactive_since && params.inactive_since > (Time.now - 30.minutes)
      'away'
    else
      'offline'
    end
  end
end