Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Rails 6 + ActionCable + Stimulus example for pushing updates to the view.

Rails 6 + ActionCable + Stimulus example for pushing updates to the view.

This example will show how to push updates to the view for a Model instance that has changed without the user having to refresh the page.

This example focuses more on getting ActionCable working with Stimulus. If you don't already have stimulus setup in your app, here's a great write up on how to set it up:

Example scenario

  • You have a Scan model with attributes.
  • You have a ScanController#show action.
  • A user is viewing a Scan through the show.html.slim|haml|erb view template.
    • http://localhost:3000/scans/1
  • When attributes change for the Scan model, we can push those changes to the view. No page reload needed.

Step 1 - Controller

Nothing new here.

# app/controllers/scans_controller.rb

class ScansController < ApplicationController
  def show
    @scan = Scan.find(params[:id])

Step 2 - Update the view

Add data-* attributes to the view that will be used by Stimulus and ActionCable.

# app/views/scans/show.html.slim

h1 Scan

.scan data-controller="scan"
  .status data-target="scan.status"
    = render partial: "scans/statuses/#{@scan.status}"
  • data-controller="scan" tells Stimulus which controller to use.
  • will be used by Stimulus and ActionCable.
  • data-target="scan.status" tells Stimulus which DOM element to update

Step 3 - Create the Stimulus controller

# app/javascript/controllers/scan_controller.js

import { Controller } from "stimulus";
import consumer from "channels/consumer";

export default class extends Controller {
  static targets = ["status"];

  connect() {
    this.subscription = consumer.subscriptions.create(
        channel: "ScanChannel",
        connected: this._connected.bind(this),
        disconnected: this._disconnected.bind(this),
        received: this._received.bind(this),

  _connected() {}

  _disconnected() {}

  _received(data) {
    const element = this.statusTarget
    element.innerHTML = data
  • static targets = ["status"]; - See data-target="scan.status" in the view template.
  • channel: "ScanChannel" - ActionCable channel used.
  • id:"id"), - See in the view template.

When data is received on the channel, this code will update the target.

  _received(data) {
    const element = this.statusTarget
    element.innerHTML = data

Step 4 - Create the ActionCable channel

# app/channels/scan_channel.rb

class ScanChannel < ApplicationCable::Channel
  def subscribed
    stream_from "scan_#{params[:id]}"

If a user is viewing a Scan with id of 1, then an ActionCable channel of scan_1 will be created.

Step 5 - Update the DOM

ActionCable.server.broadcast("scan_1", "FooBar")

You can also use a partial. Here's an example from an ActiveJob/Sidekiq job.

# app/jobs/update_scan_progress_job.rb
class UpdateScanProgressJob < ApplicationJob
  queue_as :default

  def perform(message)
    message = JSON.parse(message)
    scan_id = message["scan_id"].to_i

    scan = Scan.find(scan_id)
    scan.update(status: message["status"])

    partial = ApplicationController.render(partial: "scans/statuses/#{message["status"]}")
    ActionCable.server.broadcast("scan_#{}", partial)


If you encounter issues, verify you have the following in your application.

  • Your config/application.rb should have the following line uncommented.

    • require "action_cable/engine"
  • Your config/cable.yml file should be setup.

default: &default
  adapter: redis
  url: <%= ENV.fetch("REDIS_HOST") %>

  adapter: async

  <<: *default

  <<: *default
  • Need to have the following files:
# app/channels/application_cable/channel.rb
module ApplicationCable
  class Channel < ActionCable::Channel::Base

# app/channels/application_cable/connection.rb
module ApplicationCable
  class Connection < ActionCable::Connection::Base
  • Your app/javascript/channels/consumer.js should look like this:
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `rails generate channel` command.

import { createConsumer } from "@rails/actioncable"

export default createConsumer()
  • Your app/javascript/channels/index.js should look like this:
// Load all the channels within this directory and all subdirectories.
// Channel files must be named *_channel.js.

const channels = require.context('.', true, /_channel\.js$/)
  • The app/javascript/packs/application.js looks like this:

import "stylesheets/application"
import "controllers"
  • The app/javascript/controllers/index.js looks like this:
import { Application } from "stimulus"
import { definitionsFromContext } from "stimulus/webpack-helpers"

const application = Application.start()
const context = require.context(".", true, /\.js$/)
  • Should see @rails/actioncable in the yarn.lock and package.json files. If not, run the following command to update the files.
    • yarn add @rails/actioncable


I created this gist because I wasn't able to find an example that was clear to me on how to do this. Using the resources below, I was able to piece together the example above. Thank you to the authors of the resources below.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment