ruby-on-railsactioncableruby-on-rails-7import-maps

Rails 7 ActionCable Unable to Connect


I recently upgraded from Rails 6.1.4.6 to 7.0.2.2. With this upgrade I switched from webpacker to import maps with sprockets. My repo didn't include turbolinks or stimulus and I didn't feel like adding them now either. So I re-added UJS and most of my tests pass except the action cable feature tests. It seems I cannot get action cable to connect.

Any help would be appreciated!

Gemfile

source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby "3.0.2"

# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 7.0", ">= 7.0.2.2"
# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
gem "sprockets-rails", "~> 3.4.2"
# Use postgresql as the database for Active Record
gem "pg", "~> 1.1"
# Use the Puma web server [https://github.com/puma/puma]
gem "puma", "~> 5.0"
# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
gem "importmap-rails", "~> 1.0"
# Build JSON APIs with ease [https://github.com/rails/jbuilder]
gem "jbuilder", "~> 2.0"
# Use Redis adapter to run Action Cable in production
gem "redis", "~> 4.0"
# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
gem "bcrypt", "~> 3.1.7"
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ]
# Reduces boot times through caching; required in config/boot.rb
gem "bootsnap", require: false
# Use SCSS for stylesheets
gem "sassc-rails", "~> 2.1.2"
# FontAwesome icons for sprockets and SASS
gem "font-awesome-sass", "~> 5.15.1"
# Payment management system
gem "stripe", "~> 5.45.0"
# Email management system
gem "sendgrid-ruby", "~> 6.6.1"
# Communications (voice/sms) system
gem "twilio-ruby", "~> 5.65.0"
# HTTP request gem
gem "faraday", "~> 1.10.0"
# Enable cross-origin AJAX
gem "rack-cors", "~> 1.1.1"
# Authentication framework
gem "devise", "~> 4.8.0"
# Efficient SQL importer
gem "activerecord-import", "~> 1.3.0"
# Session store backed by an Active Record class
gem "activerecord-session_store", "~> 2.0.0"
# Production background worker
gem "sidekiq", "~> 6.4.0"
# Extention for sidekiq cron jobs
gem "sidekiq-scheduler", "~> 3.1.1"
# Segment Analytics Gem
gem "analytics-ruby", "~> 2.4.0"

group :development, :test do
  # Debugging tool
  gem "pry", "~> 0.14.1"
  # Testing framework
  gem "rspec-rails", "~> 5.0.0"
  # Broswer webdriver for testing
  gem "selenium-webdriver", "~> 3.142", ">= 3.142.7"
end

group :development do
  # Add speed badges [https://github.com/MiniProfiler/rack-mini-profiler]
  gem "rack-mini-profiler", "~> 2.0"
  # Use console on exceptions pages [https://github.com/rails/web-console]
  gem "web-console", "~> 4.2.0"
  # Linter
  gem "rubocop-rails", require: false
end

group :test do
  # Testing frontend integration
  gem "capybara", "~> 3.36.0"
  # Testing coverage
  gem "simplecov", "~> 0.21.2", require: false
  # Disallow API calls during testing
  gem "webmock", "~> 3.14.0"
  # Make initial API call and save as fixtures
  gem "vcr", "~> 6.0.0"
  # Generate fixtures for testing
  gem "factory_bot_rails", "~> 6.2.0"
  gem "faker", "~> 2.19.0"
end

config/cable.yml

development:
  adapter: async

test:
  adapter: test

production:
  adapter: redis
  url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
  channel_prefix: my_app_name

config/routes.rb

mount ActionCable.server => "/cable"

config/importmaps.rb

pin "application", preload: true
pin "@rails/actioncable", to: "actioncable.esm.js"
pin_all_from "app/javascript/channels", under: "channels"

app/assets/config/manifest.js

//= link_tree ../images
//= link_directory ../stylesheets .scss
//= link_tree ../javascript .js
//= link_tree ../../../vendor/javascript .js
//= link application.css
//= link channels/consumer.js
//= link channels/index.js
//= link channels/messages_channel.js

app/javascripts/application.js

import "@rails/actioncable"
import "channels"

app/javascripts/channels/index.js

import "channels/market_channel"
import "channels/messages_channel"

app/javascripts/channels/consumer.js

import { createConsumer } from "@rails/actioncable"

export default createConsumer()

app/javascripts/channels/messages_channel.js

import consumer from "channels/consumer"

consumer.subscriptions.create("MessagesChannel", {
  connected() {
    console.log('connected');
  },

  disconnected() {
    // Called when the subscription has been terminated by the server
  },

  received(data) {
    ... js code ...
  }
});

Log showing connection to /cable

Started GET "/cable" for ::1 at 2022-03-09 08:58:22 -0700
Started GET "/cable/" [WebSocket] for ::1 at 2022-03-09 08:58:22 -0700
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
  User Load (0.4ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2  [["id", 2], ["LIMIT", 1]]
  ↳ app/channels/application_cable/connection.rb:14:in `find_verified_user'
Registered connection (Z2lkOi8vY2FsZW5kYXJpemUvVXNlci8y)
MessagesChannel is transmitting the subscription confirmation
MessagesChannel is streaming from messages:2
MarketChannel is transmitting the subscription confirmation
MarketChannel is streaming from market_channel

No /cable connection in WS tab in dev console Network Websocket Tab

Importmap scripts in header Importmap scripts in header


Solution

  • Figured out the problem was because I had two applications.js files. One in app/assets/javascripts/ and another in app/javascript. Sprockets was serving my asset version of application.js due to my manifest pointing there. I adjusted the manifest and deleted the secondary application.js and all is working.