2019-09-27 10:10 — By Erik van Eykelen
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
Goals
Flow
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!