Rails: how to show user's "last seen at" time? - ruby-on-rails

I'm using devise which stores current_sign_in_at and last_sign_in_at datetimes.
But lets say a user logged in a month ago but last viewed a page 5 minutes ago?
Is there a way I can display that ("User last seen 5 minutes ago").

How about this:
Create a migration to add a new field to users to store the date and time the user was last seen:
rails g migration add_last_seen_at_to_users last_seen_at:datetime
Add a before action callback to your application controller:
before_action :set_last_seen_at, if: proc { user_signed_in? }
private
def set_last_seen_at
current_user.update_attribute(:last_seen_at, Time.current)
end
This way, on every request (i.e. activity) that the current user performs, his/her last seen at attribute is updated to the current time.
Please note, however, that this may take up some of your app's resources if you have many users who are logged in, because this will execute before every controller action requested by someone who is logged in.
If performance is a concern, consider adding the following throttle mechanism to step 2 (in this example, throttling at 15 minutes):
before_action :set_last_seen_at, if: proc { user_signed_in? && (session[:last_seen_at] == nil || session[:last_seen_at] < 15.minutes.ago) }
private
def set_last_seen_at
current_user.update_attribute(:last_seen_at, Time.current)
session[:last_seen_at] = Time.current
end

To improve performance of the previous answer:
don't use session, as user already loaded with warden and all the attributes are accessible
update_attribute runs callbacks and updates updated_at attribute, and update_column not
to improve performance, better to use background workers, like ActiveJob/Resque/Sidekiq
to prevent from the high DB locking, better to create a seperate table, associated with users table, and write accesses there
Updated code:
before_action :set_last_seen_at, if: proc { user_signed_in? && (user.last_seen_at.nil? || user.last_seen_at < 15.minutes.ago) }
private
def set_last_seen_at
current_user.update_column(:last_seen_at, Time.now)
end
Devise plugin makes similar behaviour happen (just last seen, without optimizations): https://github.com/ctide/devise_lastseenable

Related

Ruby on Rails devise, how to put a limit on how often a user can change their username

`I have recently started working on a rails app and using devise as authentication. but I have ran into a wall. I would like to know if there's a way to set a period limit on how often a user may update their username. For example, if a User update their username today, they shouldn't be able to update it again until a 30day period has passed.
I have looked through devise docs, but nothing address that functionality, I have also search SO and the web but to no avail.
Any help would be very much be appreciated on how to go about it, or at least be pointed in the right direction. Thanks in advance!
I have added to the user model
after_save :name_last_updated
def name_last_updated
if self.username_changed?
self.name_last_updated_at = Time.now
end
but this does not update the colunm name_last_updated_at. any clues of what i am doing wrong will be helpful ^^ thanks!
so, after messing around with a few codes i figured an alternative way to go about this. I created a new column to track new time the user should be able to see the form to update his username.
def username_next_update
self.username_next_update_at = self.username_last_update_at + 2.minutes
end
for learning and testing purposes i added +2.minutes
and i did a before_save on it.
In my view i wrapped it around an if and else statement although i am quite confuse with the logic.
<% if current_user.username_next_update_at < time.zone.now %>
********
<% end %>
but i expected to it to work only if it was > sign instead of <. any tips will be helpful or any better alternatives :)
You need to create it from scratch.
Create a new column in the users table, call it name_last_updated, when the user updates their name for the first time, set that column to today's date. then every time a user wants to update their name, check that column and compare with today's date and see if 30 days have passed:
if Date.today - user.name_last_update < 30
#display error
end
We can use user updated_at column created by the devise gem and add a callback method in the user model to make sure that we call this method every time the user model is updated.
before_update { |user| user.write_attribute if user.is_permitted? }
def write_attribute
self.user_name = params[:user][:user_name]
end
def is_permitted?
if self.username_changed?
Date.today - updated_at < 30
end
end
You should use the before_update and specify which record has to activate the callback instead of using after_save.
I would do something like this:
before_update :name_last_updated, if: :username_changed?
def name_last_updated
if (self.name_last_updated_at.to_date + 30.days) < Date.today
self.update(name_last_updated_at: Time.now)
end
end

How to track if user make any actions Rails app

I'm creating app with lessons/tests after it and right now I need to create a report card with some information. I want to add column to Users which will track time spent online(without idle time). For example student is pressing any buttons on site(just for example), it means that student is online and the (current_user.online_time should not stop), but if student did nothing more than 5 minutes - (current_user.online_time should stop). I found a gem devise lastseenable, but can't imagine right know how to make it work according to my wishes. Gem tracks when U did any actions only with User(create/update/delete/ or with models which belongs to user), without tracking any other moves. Can someone give me any ideas?
It is certainly possible to put together a simple user tracking feature without using an external gem specifically built for this purpose. Here is a list of the required implementation steps:
1. Add a total_time_online and a last_seen_at field to User
total_time_online will contain the number of seconds the user was seen online
last_seen_at will hold the date and time the user last interacted with the site
2. Add an active_now! method to User
This method will be called whenever the user is interacting with the site. It is responsible for incrementing the total_time_online value and updating the last_seen_at field:
class User
ActivityThreshold = 5.minutes
# ...
def active_now!
time_since_last_activity = [Time.now - last_seen_at, 0].max
if time_since_last_activity <= ActivityThreshold
self.total_time_online ||= 0
self.total_time_online += time_since_last_activity
end
self.last_seen_at = Time.now
save!
end
# ...
end
This will only increment the total_time_online if the last interaction was less than 5 minutes ago.
3. Call active_now! on the current user on every request
A global before_action should do the trick:
class ApplicationController < ActionController::Base
# ...
before_action :record_user_activity
# ...
private
# ...
def record_user_activity
current_user.active_now! if current_user
end
end

Create a logic captcha from scratch

I haven't touched a scrap of code yet, but here's my thoughts on how to do this:
Create a :interactions entry in my session hash. This will contain an array of time stamps. Every time a user goes through any action, the time they did this will be appended to the :interactions entry. The array will be initialized in my sessions controller, and timestamps appended to it via a filter in my application controller:
class SessionsController < ApplicationController
def create
create_session
session[:interactions] = []
end
end
class ApplicationController < ActionController::Base
after_action :log_time
private
def log_time
session[:interactions] << Time.now.to_i
end
end
Then, create another action in my application controller, the one tasked with launching the recaptcha if the user's behaviour is suspicious. All it does is see when we have 20 entries in our session[:interactions] array, find out the time elapsed between each pair of consecutive entries, and then find the average time elapsed between these interactions. If the average time is under two minutes, the recaptcha is launched. The session[interactions] array is then reset.
class ApplicationController < ActionController::Base
after_action :log_time
before_action :launch_captcha
private
def launch_captcha
if session[:interactions].length == 20
elapsed = []
session[:interactions].each_slice(2) do |a, b|
elapsed << b - a
end
total = elapsed.inject(:+)
average = total / 20
if total < 120
# this a part I'm really not sure how to do:
# various instance variables should be populated here
redirect_to 'application/launch_captcha.html.erb'
end
session[:interactions] = []
end
end
def log_time
session[:interactions] << Time.now
end
end
Now, the fact the session[:interactions] is reset may be a bit of a weakness; all bets are off for those twenty interactions. But I want to build on the above logic, maybe add session[:captchas_sent], to the session hash (or even have captchas_sent as a column and save it to the user's record), and if the session[:captchas_sent] is x amount or y amount, warnings or temporary bans could come into effect.
What are your thoughts on the above way of monitoring user behaviour?
Here's where my knowledge of rails is starting to break down though. Once I've redirected the user to the recaptcha page, how should I proceed? I have two tables, questions and answers with a has_many belongs_to relationship between them respectively.
So a random question will come from my questions table, and then I'll have a form that pertains to an answer. It will be an ajax form, and have just one field, a text field for the answer. The action the form links to, human test, will see if the answer given is equal to one of the question's answers. But how should the question's id be passed into this action? It couldn't be a simple hidden field, because once the spammer knows the answer to one question, his script could always set the id to that one question. So the params hash or the sessions hash maybe? I need some advice here guys.
I also don't really know how the human test method should proceed once the it finds the user's answer is equal to one of the question's answers:
Let's say the user is submitting a comment. They fill in the comment, hit the submit button, the launch_captcha action kicks in, and redirects them to 'application/launch_captcha.html.erb'. What has happened to the data in the comment create form? Once they've answered the captcha correctly, how should the human_test method proceed? How could it go on to submit their comment as usual? I just don't know how to do that...I need to create an action that is called before the create action of a form...and..argh I just don't know. Help guys!

Devise: rememberable means that last_sign_in_at is not updated by trackable

I have being using Devise, and relying on last_sign_in_at of the user model to work out if my customers have not returned within X days. However, I recently discovered that last_sign_in_at is only updated when an actual form log in event occurs, as opposed to when a user is logged in automatically due to the inclusion of rememberable.
If want to ensure that last_sign_in_at is updated each time a user logs in (a new browser session), regardless of whether they used a form to log in or were automatically logged in by the rememberable cookie, how would I go about doing this in a Devise-compatible way?
Taking Matthew's solution, I think the code should be the following (note the not-operator before the session[:logged_signin]):
before_filter :update_last_sign_in_at
protected
def update_last_sign_in_at
if user_signed_in? && !session[:logged_signin]
sign_in(current_user, :force => true)
session[:logged_signin] = true
end
end
The trackable hook is from Warden's after_set_user hook -- what you could do to easily remedy this is set a before_filter to call sign_in.
This could be optimized, but test to see if using
before_filter proc{ sign_in(current_user, :force => true) }
updates the last_signed_in_at timestamp.
Devise: rememberable means that last_sign_in_at is not updated by trackable
Expanding on previous solutions, the problem with them would be that if the user signs in normally, they will "sign in twice". Which will set last_sign_in_at to the same (or almost the same) value as current_sign_in_at.
On my site, I use last_sign_in_at to let the user know what has happened since last time they visited the site, and as such I need it to be somewhat accurate. Also, it logs +1 login count.
Also, there are people (like myself) who leave a browser window open for days without closing it (and hence never clearing the session flag). For metric purposes etc, it can be useful if such user behavior sometimes refresh the current_sign_in_at time.
The below variants will remedy these things.
class ApplicationController < ActionController::Base
before_filter :update_sign_in_at_periodically
UPDATE_LOGIN_PERIOD = 10.hours
protected
def update_sign_in_at_periodically
if !session[:last_login_update_at] or session[:last_login_update_at] < UPDATE_LOGIN_PERIOD.ago
session[:last_login_update_at] = Time.now
sign_in(current_user, :force => true) if user_signed_in?
end
end
end
However, when I try the above, using Devise 3.2.4, I do get a new login when it auto-logins by cookie (login-count +1 and current_sign_in_at being set). So, I'm left with only the issue of wanting the tracking to periodically update even for users which keep the session open.
class ApplicationController < ActionController::Base
before_filter :update_sign_in_at_periodically
UPDATE_LOGIN_PERIOD = 10.hours
protected
def update_sign_in_at_periodically
# use session cookie to avoid hammering the database
if !session[:last_login_update_at] or session[:last_login_update_at] < UPDATE_LOGIN_PERIOD.ago
session[:last_login_update_at] = Time.now
if user_signed_in? and current_user.current_sign_in_at < 1.minute.ago # prevents double logins
sign_in(current_user, :force => true)
end
end
end
end
On the application_controller you can set a before_action that checks if the current_sign_in_at of the current user is longer then X ago. If it is, then use sign_in(current_user, force: true) that updates the current_sign_in_at.
before_action :update_last_sign_in_at
def update_last_sign_in_at
return unless user_signed_in? && current_user.current_sign_in_at < 12.hours.ago
sign_in(current_user, force: true)
end
I use it so to detect inactive users (not signed in for 6 months) and delete them. #GDPR
AFAIK you can also use update_tracked_fields! on that current_user model.

Rails and Authlogic. Show currently logged in users

I would like to have the list of currently logged in users.
This code doesn't work :
<% UserSession.all.each do |user_session| %>
<% end %>
#syed-aslam has a good solution, but you could just let Authlogic do the work. Check out the module Authlogic::ActsAsAuthentic::LoggedInStatus which defines two scopes: logged_in, logged_out
Your code becomes:
<% User.logged_in.each do |user| %>
<% end %>
P.S. I would normally link to the RDoc instead of source code, but the RDoc seems to have problems at the moment.
Authlogic gives you all kind of automatic columns that you don’t really need to update or maintain on your own, they are maintained by the actual code flow of Authlogic itself.
Those fields can contain some basic functionality related issues like the number of login attempts made, the ip address from which the attempt was made an or even what was the ip address the last time that user logged in. fun.
The magic column that will help us find who is probably online is the one called last_request_on, which basically indicates when was the last time that user made a request to your application.
The second parameter we’ll need in order to make a more accurate selection, is the configuration option named logged_in_timeout, which sets the timeout after which a stale session will be expired, by default it will expire after 10 minutes.
so if you set your session expiry to 30 minutes:
class User << ActiveRecord::Base
acts_as_authentic do |c|
c.logged_in_timeout 30.minutes
end
end
searching for those users is pretty easy:
module OnlineUsers
def count_online_users
User.count(:conditions => ["last_request_at > ?", 30.minutes.ago])
end
end
Why not creating a field called currently_active in the user model and update it to true once a session is created, and update to false once the session is destroy.
You can then call User.where(currently_active: true) gives you the users that online.
You cannot get UserSession for all user, UserSession is created every time user sends request and is not remembered between requests.
However you can show users which logged in some period of time (if you have last_logged_in column updated on every signin)
Logged in last 15 minutes:
<% User.find("last_logged_in < ?", 15.minutes.ago ).each do |user| %>
I wrote after_create and before_destroy callbacks in UserSession model.
In after_create callback, I wrote the user id of the user logging in to a text file (self.user.id) and in before_create callback I deleted the same. To check the activity of the user, I read the text file and checked the presence of the user id in that file.

Resources