Hotwire: Online user tracking with turbo streams
Related videos
In today's tutorial, we're going to walk through implementing real-time online statuses using Turbo in a Rails application. We'll start with a basic app that lists users and displays whether they're currently online, and then work through the process of updating these statuses in real time when users sign in and out.
App Overview
Our demo application has a simple list of users that can sign in and out without any password. The user list includes a status for each user showing if they are currently online, when they were last seen or if they never signed in before. The app consists of a User
model with a username, a status (online/offline) and a last_online
timestamp attribute.
The user model includes an online?
method that checks if the user's status is "online". We also have a UsersController
and a SessionsController
that handle fetching users and signing in/out, respectively. We will use Action Cable and Turbo to add real-time updates to the online statuses.
Setting Up Action Cable
First, make sure Action Cable is installed by running:
yarn add @rails/actioncable
Now, let's create an OnlineChannel
that will override the default Turbo channel, allowing us to utilize Action Cable alongside Turbo.
Create a new file called online_channel.rb
in your app/channels
directory and paste the following code:
class OnlineChannel < Turbo::StreamsChannel
def subscribed
current_user&.update!(status: 'online', last_online_at: Time.current)
end
def unsubscribed
current_user&.update!(status: 'offline')
end
end
Here, we define subscribed
and unsubscribed
methods that update the user's status and last_online_at
timestamp when the WebSocket connection opens or closes.
Identifying the Current User
Next, we need to update our ApplicationCable::Connection
class to set a current user. In the app/channels/application_cable
directory, open the connection.rb
file and add the following:
# app/channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
private
def find_verified_user
if verified_user = User.find_by(id: cookies.encrypted[:user_id])
verified_user
else
# reject_unauthorized_connection
nil
end
end
end
end
This code adds a current_user
attribute to identify connections and defines a find_verified_user
method that looks up the user by their ID using an encrypted cookie. If there is no logged-in user, we reject the connection and prevent the WebSocket from loading.
Subscribing to the Turbo Stream
Now we need to subscribe to this OnlineChannel
in our application.html.erb
layout file. You can add this to anywhere in the <body>
tag.
<%= turbo_stream_from "online_users", channel: OnlineChannel %>
This will subscribe the user to the OnlineChannel
, triggering the subscribed and unsubscribed callbacks above. At this point, if we sign in and out as different users, we should see the online statuses updating in real time!
Making User Model Live Update
To make the user model reactive to changes, we can use Turbo's broadcasting feature. In the User
model, add the following code:
# app/models/user.rb
class User < ApplicationRecord
after_update_commit { broadcast_replace_to "online_users" }
# other user model code...
end
This line of code tells Turbo to broadcast the updated user status to the online_users
channel whenever the model is updated. Turbo will then look for a user partial, like _user.html.erb
, and replace the corresponding HTML content with the updated information.
Additionally, you may want to update the user list when new users are created or deleted. Add the following code to the User
model:
after_create_commit { broadcast_prepend_to("online_users", target: "online-users") }
after_destroy_commit { broadcast_remove_to("online_users") }
Also, add an ID to the index.html.erb
view to identify the target for these broadcasts:
<h1 class="text-2xl mb-8 font-semibold">All members</h1>
<ul id="online-users" role="list" class="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
<%= render @users %>
</ul>
Now, whenever a new user is created or deleted, the user list will update automatically.
Wrapping Up
With the help of Turbo and Action Cable, we've added real-time interactivity to our Rails app, allowing online statuses to update in real time without writing much custom code. Turbo provides an efficient way of leveraging WebSockets and Action Cable together, making it easy to develop rich, interactive applications with minimal effort.
If you enjoyed this tutorial and want to see more like it, consider subscribing. And if you have any content ideas or suggestions, feel free to leave a comment below. Happy coding!