Hotwire: Online user tracking with turbo streams

16 Jan, 2023

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!

Discussion (0)

To comment you need to sign up for a free account or sign in.

Get new videos in your inbox weekly!

Be the first to hear about new courses and videos launching. You’ll only receive relevant news, no spam.