Notes to self

Moving ActionCable over to Webpacker

This week, I upgraded a little demo application for my book Deployment from Scratch from Rails 6 to Rails 6.1. Since I showcase WebSockets with ActionCable and Redis, I needed to move the ActionCable CoffeeScript from Sprockets to Webpacker.

I started with dependencies. The original application could lose uglifier as Sprockets’ JavaScript processor and coffee-rails in favour of JavaScript. I replaced them with webpacker gem in Gemfile:

gem 'webpacker', '~> 5.4'

Once I generated a new Gemfile.lock, I could run a webpacker:install tasks that creates many files (which I won’t get into here):

$ rails webpacker:install

In case you won’t see the new Webpacker tasks, make sure to delete the Rails cache:

$ rails tmp:cache:clear

It took me a while to realize why I don’t see this Webpacker Rake task.

Once that’s done, let’s see how to move the JavaScript entry point file.

// app/assets/javascripts/application.js
//= require rails-ujs
//= require activestorage
//= require turbolinks
//= require_tree .

All these requirements should now happen in the new app/javascript directory:

// app/javascript/packs/application.js
import Rails from "@rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "@rails/activestorage"
import "channels"

Rails.start()
ActiveStorage.start()

After I had my new application.js ready, I changed javascript_include_tag to javascript_pack_tag in views:

<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>

Then I updated the channels. I went from this:

// app/assets/javascripts/cable.js
// 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.
//
//= require action_cable
//= require_self
//= require_tree ./channels

(function() {
  this.App || (this.App = {});

  App.cable = ActionCable.createConsumer();

}).call(this);

To new channels structure with channels/index.js and channels/consumer.js:

// app/javascript/channels/index.js
// Load all the channels within this directory and all subdirectories.
// Channel files must be named *_channel.js.

const channels = require.context('.', true, /_channel\.js$/)
channels.keys().forEach(channels)


// app/javascript/channels/consumer.js
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `bin/rails generate channel` command.

import { createConsumer } from "@rails/actioncable"

export default createConsumer()

And then I rewrote my original subscription file that looked like this:

// app/assets/javascripts/cable/subscriptions/document.coffee
App.cable.subscriptions.create { channel: "DocumentChannel" },
  connected: () ->

  received: (data) ->
    console.log("Received data.")

    alert(data["title"])

To a JavaScript version using the previous consumer.js file:

// app/javascript/channels/documents_channel.js
import consumer from "./consumer"

consumer.subscriptions.create(
  { channel: "DocumentChannel" },
  {
    connect() {},
    received(data) {
      console.log("Received data.")
      alert(data["title"])
    }
  }
)

At this point the all the new files are in place, I just had to go and delete the old app/assets/javascript directory:

$ rm -rf app/assets/javascripts

And remove it from the manifest (the second line):

// app/assets/config/manifest.js
//= link_tree ../images
//= link_directory ../javascripts .js
//= link_directory ../stylesheets .css

Although it’s a small app with only one channel, you might find this useful if you didn’t move to Webpacker yet.

Check out my book
Interested in Ruby on Rails default testing stack? Take Minitest and fixtures for a spin with my latest book.

Get Test Driving Rails while it's in prerelease.

by Josef Strzibny
RSS