Typing Indicators with Cubism
Tags: cubism, cableready, rails
Published: 2022-01-11 10:57:05

---
title: Typing Indicators with Cubism
published: true
date: 2022-01-11 00:00:00 UTC
tags: cubism,cableready,rails
canonical_url: https://blog.minthesize.com/typing-indicators-with-cubism
---

{% youtube 2Vp8Hk_mewU %}

In this quick tutorial, I’m going to acquaint you with [Cubism](https://github.com/julianrubisch/cubism/), a lightweight, CableReady based way to add presence indicators to your Rails models.

Before we get too far into the weeds, though, here’s a quick overview of how it works conceptually:

[![../assets/images/posts/2022/2022-01-11-cubism-sequence.png](https://blog.minthesize.com/assets/images/posts/2022/2022-01-11-cubism-sequence.png)](../assets/images/posts/2022/2022-01-11-cubism-sequence.png)

For this demo I’ve set up a Rails 7 app using **esbuild** for JavaScript bundling, and installed (and configured) Kredis, since that is a dependency of Cubism.

```
$ rails new cubism_demo -j esbuild


$ bundle add kredis
$ bin/rails kredis:install

```

### Set Up a Minimal Rails App

To demonstrate how Cubism manages user presence, we need a minimum of two resources: A user, and something that user is present on. So first, let’s generate a `User` model with a `username` attribute. We’ll add two dummy users, one named Julian, and one named Andrew (the abundance of Andrews in my life warranted this choice 😬).

#### User Model

```
$ bin/rails g model User username:string
$ bin/rails db:migrate


julian = User.create(username: "Julian")
andrew = User.create(username: "Andrew")

```

#### Message Scaffold

Next we’ll add a scaffold for a `Message` resource, with a reference to a user and a `content` attribute. Let’s also add a dummy message here in the Rails console:

```
$ bin/rails g scaffold Message user:references content:string
$ bin/rails db:migrate


Message.create(content: "test", user: julian)

```

#### Prune MessagesController and Show View

We don’t need any other routes than the `show` route for demonstration purposes, so we clean everything else out. Since I don’t want to set up actual authentication for this trivial example, let’s simulate setting the current user via a request parameter.

```
# app/controllers/messages_controller.rb

class MessagesController < ApplicationController
  before_action :set_message, only: %i[show]
  before_action :set_user, only: %i[show]

  # GET /messages/1 or /messages/1.json
  def show
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_message
      @message = Message.find(params[:id])
    end

    def set_user
      @user = User.find(params[:user_id])
    end

    # Only allow a list of trusted parameters through.
    def message_params
      params.require(:message).permit(:user_id, :content)
    end
end


<!-- app/views/messages/show.html.erb -->
<p style="color: green"><%= notice %></p>

<%= render @message %>

```

### Install Cubism

Now we’re ready to actually **install** Cubism. We do that by adding both the Rubygem as well as the npm package.

```
$ bundle add cubism
$ yarn add @minthesize/cubism

```

In addition to importing said JavaScript module in `application.js`, we also need to initialize CableReady with an ActionCable `consumer`, which we obtain from `turbo-rails`:

```
// app/javascript/application.js

// Entry point for the build script in your package.json
import "@hotwired/turbo-rails"
import "./controllers"

import { cable } from "@hotwired/turbo-rails";

import CableReady from "cable_ready";
import "@minthesize/cubism";

// top level await is not supported in esbuild
(async () => {
  const consumer = await cable.getConsumer()
  CableReady.initialize({ consumer });
})()

```

### Server-Side Preparations

To wire up the server-side part of Cubism, we need to do two things:

Tell it which class to user for user lookup by including `Cubism::User`. Typically, this will be your `User` class, but it doesn’t have to be. You could equally set up `Team` for tracking presence, etc.

```
# app/models/user.rb

class User < ApplicationRecord
  # establishes the class Cubism uses for user lookup
  include Cubism::User
end

```

Include the `Cubism::Presence` module in any class we want to track presence for, in our case the `Message` class.

```
# app/models/message.rb

class Message < ApplicationRecord
  # tracks users present on an instance of this model
  include Cubism::Presence

  belongs_to :user
end

```

And that’s really it for server-side code. Elegant, isn’t it? 💅🏻

### Display Present Users

Let’s get a first impression of how this can be used to display a list of current users by adding a Cubicle element to the message’s show view. We can use the included `cubicle_for` helper for this, which takes the model and the current user as arguments, and yields the present `users` array to a block. Note that I’ve set `exclude_current_user` to false, because the default would be to exclude it, Google-Drive style.

```
<!-- app/views/messages/show.html.erb -->
<p style="color: green"><%= notice %></p>

<%= render @message %>

<%= cubicle_for @message, @user, exclude_current_user: false do |users| %>
<%= users.map(&:username).to_sentence %> <%= users.size > 1 ? "are" : "is" %> watching this message.
<% end %>

```

### Display Typing Indicators

Finally, let’s transform this from tracking every user who _views_ our message to tracking users who are _typing_ into a text field. To simulate a comment for, let’s just add a plain text field (note that this conveniently adds a DOM ID of `comment`).

To change the appear/disappear behavior, we add a couple of options to our helper. First, `appear` and `disappear` triggers, `focus` and `blur`. You can use every Javascript event name (custom ones, too!) for this. Finally, because we only care about events fired by a specific element (our comment field), we add a `trigger_root`, which expects a CSS selector, to scope our presence tracking. And voilà, here you go!

```
<!-- app/views/messages/show.html.erb -->
<p style="color: green"><%= notice %></p>

<%= render @message %>

<%= cubicle_for @message, @user,
    appear_trigger: :focus,
    disappear_trigger: :blur,
    trigger_root: "#comment" do |users| %>
  <% if users.present? %>
    <%= users.map(&:username).to_sentence %> <%= users.size > 1 ? "are" : "is" %> typing...
  <% end %>
<% end %>

<%= text_field_tag :comment %>

```

### Conclusion

Cubism makes it dead simple to add any kind of presence indicators to your Reactive Rails app. For the moment, it’s resource based (you can only add it to AR models), but routes-specific presence tracking is on the roadmap!