StimulusJS with Rails Action Cable and a bit of Sidekiq
Tags: stimulus, actioncable, rails, sidekiq
Published: 2018-08-31 19:48:11

---
title: StimulusJS with Rails Action Cable and a bit of Sidekiq
published: true
description: Implementation of Action Cable using StimulusJS to control page specific channel subscription
tags: stimulusjs, actioncable, rails, sidekiq
---
# Goal
Show a simple implementation of [Rails Action Cable](https://guides.rubyonrails.org/action_cable_overview.html) with the modest js framework named [StimulusJS](https://stimulusjs.org/).

For this tutorial, I will be using a simple demo Rails application, which you can find the source code for [here](https://github.com/dstull/sidekiq-actioncable-stimulus-demo/tree/actioncable).

I am going to gloss over the particulars of [Sidekiq](https://sidekiq.org/) here, and focus on the StimulusJS and Action Cable pieces.  I believe that will be most valuable here, as the other pieces have been covered many times on numerous blogs and tutorials. However, I may revisit the other items at a later date.

I'll break this down into 2 steps:
* Create Sidekiq worker and Action Cable Channel
* Setup StimulusJS 

# Create Sidekiq worker and Action Cable Channel

For this part, I want to show how an index page listing cars that have many drivers can be updated when a driver's name changes or the particular car they belong to.  

To accomplish that for this example, I decided to make the Sidekiq job trigger off and `after_touch` callback on the Car model.

## Car/Driver Models - the Active Record Models

```ruby
class Car < ApplicationRecord
  has_many :drivers, dependent: :destroy

  after_touch :update_driver_names

  def drivers_list
    drivers.pluck(:name).join(',')
  end

  private

  def update_driver_names
    CarsWorker.perform_async(id)
  end
end

class Driver < ApplicationRecord
  belongs_to :car, touch: true

  delegate :name, to: :car, prefix: true
end
```

In the above file, I am triggering the `after_touch` callback named `update_driver_names` on the Car model by adding `touch: true` to the Driver model. 
 The `update_driver_names` method reaches out to Sidekiq and calls an async job called `CarsWorker.perform_async`, sending the `id` of the Car that the Driver has assigned.

## CarsWorker ([cars_worker.rb](https://github.com/dstull/sidekiq-actioncable-stimulus-demo/blob/actioncable/app/workers/cars_worker.rb)) - the Sidekiq Worker

```ruby
class CarsWorker
  include Sidekiq::Worker

  def perform(car_id)
    # some contrived work...
    car                = Car.find car_id
    new_driver_changes = car.driver_changes + 1
    car.update_attribute(:driver_changes, new_driver_changes)

    car_drivers = car.drivers_list
    ActionCable.server.broadcast('cars', drivers: car_drivers, car_id: car_id, driver_changes: new_driver_changes)
  end
end
```

In the above file, I am:

1. Incrementing the `driver_changes` on the driver's car and committing that on the car.
2. Finding all the drivers for that car and broadcasting out to the Action Cable channel the new drivers list as a string in `car_drivers`, along with the number of `driver_changes`.  

Here is an example of the transmission from the CarsChannel:
```
CarsChannel transmitting {"drivers"=>"Jacalyn Bauchblah,Wes Goodwin,Guy Keeling,Miss Pasquale Doyle,Candy Welch", "car_id"=>23, "driver_changes"=>4} (via streamed from cars)
```

## CarsChannel ([cars_channel.rb](https://github.com/dstull/sidekiq-actioncable-stimulus-demo/blob/actioncable/app/channels/cars_channel.rb)) - the Action Cable Channel definition

```ruby
class CarsChannel < ApplicationCable::Channel
  def subscribed
    stream_from 'cars'
  end
end
```

The above is merely the standard boilerplate channel definition that is defined [here](https://guides.rubyonrails.org/action_cable_overview.html#streams).

# Setup StimulusJS

For this part I will show the HTML erb pieces and the StimulusJS setup.

## Cars Index ([cars/index.html](https://github.com/dstull/sidekiq-actioncable-stimulus-demo/blob/actioncable/app/views/cars/index.html.erb)) - the HTML piece

```ruby
<p id="notice"><%= notice %></p>

<div class="page-header" data-controller="cars">
  <h1>Cars</h1>
</div>

<table class="table table-hover">
  <thead>
  <tr>
    <th>Name</th>
    <th>Drivers</th>
    <th>Driver Changes</th>
    <th>Make</th>
    <th>Color</th>
    <th>Model</th>
    <th colspan="3"></th>
  </tr>
  </thead>

  <tbody>
  <% @cars.each do |car| %>
    <tr id="car_id_<%= car.id %>">
      <td><%= car.name %></td>
      <td class="cars--drivers"><%= car.drivers_list %></td>
      <td class="cars--driver-changes"><%= car.driver_changes %></td>
      <td><%= car.make %></td>
      <td><%= car.color %></td>
      <td><%= car.model %></td>
      <td><%= link_to 'Show', car %></td>
      <td><%= link_to 'Edit', edit_car_path(car) %></td>
      <td><%= link_to 'Destroy', car, method: :delete, data: { confirm: 'Are you sure?' } %></td>
    </tr>
  <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Car', new_car_path %>
```

The important part to point out in the above is the `data-controller` data attribute.  Setting this to the name of the StimulusJS controller, `cars`, will then cause it to reach out and invoke the `cars_controller.js` connect function upon page render; subscribing the user to the cars action cable channel.  There is documentation on the StimulusJS [website](https://stimulusjs.org/) explaining how that part works.

## Cars Controller ([cars_controller.js](https://github.com/dstull/sidekiq-actioncable-stimulus-demo/blob/actioncable/app/javascript/controllers/cars_controller.js)) - the StimulusJS Controller

```javascript
import { Controller } from 'stimulus';
import createChannel from '../exports/cable';

export default class extends Controller {
  connect() {
    this.initChannel();
  }

  initChannel() {
    createChannel('CarsChannel', {
      received(data) {
        const carRow = $(`#car_id_${data.car_id}`);
        const driverChanges = carRow.find('.cars--driver-changes');
        const drivers = carRow.find('.cars--drivers');
        driverChanges.text(data.driver_changes);
        drivers.text(data.drivers);
      },
    });
  }
}
```

In the above, we:

1. Importing the basic cable setup from `exports/cable.js`
2. Init the Channel from the connect function, which is fired whenever we land on the cars index page due to the `data-controller` data attribute.
3. Update the cars index page when a messages is received on the `CarsChannel`. 

## Cable Javascript ([cable.js](https://github.com/dstull/sidekiq-actioncable-stimulus-demo/blob/actioncable/app/javascript/exports/cable.js)) - the basic Action Cable JS setup that is imported when needed

```javascript
import cable from 'actioncable';

let consumer;

export default function (...args) {
  if (!consumer) {
    consumer = cable.createConsumer();
  }

  return consumer.subscriptions.create(...args);
}
```

The above is the initial/standard Action Cable setup.  I went the 'extra mile' here and also used yarn to install [actioncable](https://github.com/dstull/sidekiq-actioncable-stimulus-demo/blob/actioncable/yarn.lock#L166), ensuring it was the same version as Rails.  This helps keep me completely out of the asset pipeline/sprockets area.

# In Closing...

I particularly wanted to get completely out of the Rails asset pipeline/sprockets setup and be page specific about my channel subscription.

I hope this short demo is helpful to someone.  I searched many places to gather the bits and pieces of how to string this together, and felt I should share with the community that I have benefited so much from myself. I had only a few hours to throw this together before I had to get back to being a parent :) ...perhaps later I will add a blog on how I went about implementing testing all of this from soup to nuts.

The basis for some of this work came from [this](https://evilmartians.com/chronicles/evil-front-part-3) wonderful blog by Evil Martians.