2019-09-27 10:10

A Basic Rails Action Cable Example

In this post I'll show you how to setup and use Rails Action Cable. While there are many articles about Action Cable I felt there's room for a short and practical how-to. I'll probably do a follow-up article where I'll use AnyCable instead.

Prerequisites

  • A fresh Rails 6 app

Goals

  • Sending a message to a specific user
  • Broadcasting a message to all "connected" users

Flow

  • A controller action generates a random number, acting as a user ID
  • The action renders a page, displaying this random ID
  • The page has two buttons:
    • A "Page me" button. Clicking this button tells the server to broadcast a message back to this specific user.
    • A "Page all" button. Clicking this button broadcasts a message to all users.

If you open a second browser tab you'll be able to verify that broadcasting to a specific user and to all users works as advertised.

Code

I must admit I had a tiny hiccup getting Action Cable working on my local machine due to the fact that the Async adapter would not transmit anything.

Once I switched from Async to Redis the problem was solved. Since not using Async on your production environment is recommended anyway this is not a big deal. In fact, you should always strive for as much parity between your development environment and your staging & production environments according the excellent The Twelve Factors manifesto.

So let's begin by adding the Redis gem to your Gems file:

gem 'redis'

Note: pin your gem versions outside an example project like this.

Next, ensure the Action Cable railtie is loaded by inspecting application.rb. If you see require 'rails/all' you're good to go. If you're like me and want to specify which railties you need and don't need then ensure the list looks more or less like this:

require "rails"
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_view/railtie"
require "action_cable/engine"
require "sprockets/railtie"
require "rails/test_unit/railtie"

Ensure action_cable/engine is present in this list.

Next up is creating a test view:

<h1>User <%= @rnd_user_id %></h1>

<p><%= button_to("Page me", page_me_path(user_id: @rnd_user_id), remote: true) %></p>

<div id="hello-user-<%= @rnd_user_id %>"></div>

<p><%= button_to("Page all", page_all_path, remote: true) %></p>

<div id="hello-all-users"></div>

Don't forget to add the necessary routes to your routes.rb file:

Rails.application.routes.draw do
  root to: 'landing#index'
  mount ActionCable.server => '/websocket'
  post :page_me, to: 'landing#page_me'
  post :page_all, to: 'landing#page_all'
end

Add this to application.js:

//= require action_cable
//= require jquery3
//= require rails-ujs

/* global $, App, ActionCable */
this.App = {}
const cablePath = document.querySelector('meta[name=action-cable-url]').getAttribute('content')
App.cable = ActionCable.createConsumer(cablePath)
App.webNotificationsChannel = App.cable.subscriptions.create(
  {
    channel: 'WebNotificationsChannel'
  },
  {
    received (data) {
      console.log(data)
      if (data.userId === null) {
        $('#hello-all-users').html(`The time for all users is ${data.time}`)
      } else {
        $(`#hello-user-${data.userId}`).html(`The time for you is ${data.time}`)
      }
    },
    connected () {
      console.log('connected')
    },
    disconnected () {
      console.log('disconnected')
    }
  }
)

Don't forget to add the cable meta tag to the application.html.erb layout. It is used by application.js to set the Action Cable endpoint:

<%= action_cable_meta_tag %>

Next, let's write the necessary code:

Add a file called web_notifications_channel.rb to the app/channels directory:

class WebNotificationsChannel < ApplicationCable::Channel
  def subscribed
    Rails.logger.info("WebNotificationsChannel subscribed")
    stream_from("web_notifications")
  end
  def unsubscribed
    Rails.logger.info("WebNotificationsChannel unsubscribed")
  end
end

Add the following code to the controller which renders the test page:

def index
  @rnd_user_id = SecureRandom.hex(6)
end

def page_me
  ActionCable.server.broadcast("web_notifications", userId: params[:user_id], time: Time.now.to_i)
  head(:no_content)
end

def page_all
  ActionCable.server.broadcast("web_notifications", userId: nil, time: Time.now.to_i )
  head(:no_content)
end

Restart the server and go to http://localhost:5000/ (the port might differ on your machine):

Open the browser's development tools pane, it should display:

connected

Also, take a look at your development.log file (e.g. via tail -f log/development.log). It should contain something like:

WebNotificationsChannel subscribed
WebNotificationsChannel is transmitting the subscription confirmation
WebNotificationsChannel is streaming from web_notifications
WebNotificationsChannel transmitting {"userId"=>nil, "time"=>1569571022} (via streamed from web_notifications)
WebNotificationsChannel transmitting {"userId"=>"b3adf6ff392f", "time"=>1569571501} (via streamed from web_notifications)

Note: "userId"=>nil is sent when clicking "Page all" and "userId"=>"..." is sent when clicking "Page me".

Now click the buttons to see if they all work. Open a second tab and check what happens inside each tab:

This is all you need to start using Action Cable!

Permalink — Comments or kudos? Find me on Twitter.

Are you a project or product manager? Ship better software with Releasewise!