Search code examples
ruby-on-railsrubyredisactioncable

Redis not connecting to channel.js


I haven't seen a solution for this anywhere online so here goes. I am setting up simple notifications using ActionCable. When I send a broadcast through either the console or through my app, ActionCable succeeds and has no errors.

    ActionCable.server.broadcast("notifications_channel_1", { notification: "Testing" })

[ActionCable] Broadcasting to notifications_channel_1: {:notification=>"Testing"} => 0

I can see it come through on my redis server as well

1605237545.900905 [1 127.0.0.1:52106] "publish" "notifications_channel_1" "{"notification":"Testing"}"

The issue is that I am not receiving any data in my channel.js, which currently just writes to console for troubleshooting purposes.

notifications_channel.js

    import consumer from "./consumer"
    
    
    consumer.subscriptions.create("NotificationsChannel", {
    
      connected() {
        console.log("connected to the server!")
      },
    
      disconnected() {
        console.log("disconnected from the server")
      },
    
      received(data) {
          console.log("Receiving:");
          console.log(data.content);
      }
      }

);

In the browser, I can see my "connected to server!" show up, so I know I am connected, but pushing the test button I have set up doesn't yield a "Receiving: [data]". Below are all of the relevant sections of code in my application:

config/initializers/redis.rb

    redis_host = Rails.application.secrets.redis && Rails.application.secrets.redis['host'] || 'localhost'
    redis_port = Rails.application.secrets.redis && Rails.application.secrets.redis['port'] || 6379
    
    # The constant below will represent ONE connection, present globally in models, controllers, views etc for the instance. No need to do Redis.new everytime
    REDIS = Redis.new(host: redis_host, port: redis_port.to_i)

environments/development.rb

      Rails.root.join('config/cable.yml')
      config.action_cable.url = "ws://localhost:3000/cable"

cable.yml

    development:
      adapter: redis
      url: redis://127.0.0.1:6379/1

index.html.erb

    <%= link_to "Test Notifications", test_path, method: :post %>

notification_test controller action

      def notification_test
        @project = Project.first
        create_notification(current_user, @project, "teammate",
                            "#{@project.name}: Added to the team! Check out your new dashboard by visiting the project!")
      end

create_notification function within application_controller.rb

      def create_notification(user, project, link, body)
        notification = Notification.new(user_id: user.id, project_id: project.id, 
                                        link: link, body: body)
        notification.save
      end

notification.rb

  after_create_commit { NotificationBroadcastJob.perform_later self }

notification_broadcast_job.rb

    class NotificationBroadcastJob < ApplicationJob
      queue_as :default
    
      def perform(notification)
        ActionCable.server.broadcast("notifications_channel_#{notification.user_id}", { 
        notification: render_notification(notification) })
      end
    
      private
    
      def render_notification(notification)
        ApplicationController.renderer.render(partial: 'notifications/alerts', locals: { notification: notification })
      end
    end

notifications_channel.rb

    class NotificationsChannel < ApplicationCable::Channel
      def subscribed
        stream_from "notifications_#{current_user.id}"
    
      end
    
      def unsubscribed
        stop_all_streams
      end
    end

notifications/_alerts.html.erb

    <% if defined?(notification) %>>
      <div class="uk-container">
        <div uk-alert="animation">
          <a class="uk-alert-close" uk-close></a>
          <%= notification.body %>
        </div>
      </div>
    <% end %>

application.html.erb

    <%= render 'notifications/alerts' %>

I have seen people say that this is a rails 5 issue that was resolved with rails 6, but I am running rails 6 and still having this issue.

    rails --version

Rails 6.0.3.4

Is there a fix for this? How do I get that beautiful "Receiving: [data]" to show up in my console?


Solution

  • The problem stems from how you are subscribing to the NotificationsChannel you didn't pass a user id, so you are justing subscribing to "NotificationsChannel"

    If you replace your broadcast with

    ActionCable.server.broadcast("notifications_channel", { notification: "Testing" })
    

    And your stream with

    class NotificationsChannel < ApplicationCable::Channel
      def subscribed
        stream_from "notifications_channel"
      end
    end
    

    Then the receive callback in your client-side subscription would work but since what you want is to send notifications to a particular user there are several ways to accomplish this

    1. Use stream_for and just stream to the model directly this makes it super easy for you
    2. Subscribe to the exact channel you are creating.

    If you opt-in for option one then you just have to replace stream_from with stream_for

    class NotificationsChannel < ApplicationCable::Channel
      def subscribed
        stream_for current_user
      end
    end
    

    No changes required here, keep your client-side code as is.

    If you opt-in for option two, then stream using

    class NotificationsChannel < ApplicationCable::Channel
      def subscribed
        stream_from "notifications_#{params[:user_id]}"
      end
    end
    

    And you client should create a subscrition like so

    consumer.subscriptions.create({channel: "NotificationsChannel", user_id: 1}), {
      connected() {
        console.log("connected to the server!");
      },
    
      disconnected() {
        console.log("disconnected from the server");
      },
    
      received(data) {
        console.log("Receiving:");
        console.log(data.content);
      },
    });