In this quick tutorial, I’m going to acquaint you with 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:
$ 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 😬).
$ bin/rails g model User username:string $ bin/rails db:migrate
julian = User.create(username: "Julian") andrew = User.create(username: "Andrew")
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 %>
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
application.js, we also need to initialize CableReady with an ActionCable
consumer, which we obtain from
To wire up the server-side part of Cubism, we need to do two things:
Tell it which class to use 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
Cubism::Presence module in any class we want to track presence for, in our case the
# 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
To change the appear/disappear behavior, we add a couple of options to our helper. First,
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 %>
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!