Action Cable: components checklist

January 27, 2016


Here are the minimum code changes required to provide websocket capability in a Rails 5.0.0 app. This is based on a minimal alert app.

Disclaimer: Of course this is all rapidly changing in Rails. This was correct for rails master branch at the time of writing this post.

Prerequisites:

  1. gemfile: add em-hiredis, redis, and make sure you have puma 2.15.3 or greater
...
gem 'rails', '>= 5.0.0.beta1.1'
gem 'em-hiredis'
gem 'redis'
gem 'puma', '>= 2.15.3'
...

  1. install redis:
$ sudo apt-get -y install redis-server

Code changes Starting from the outside in:

  1. app/views/layouts/application.erb:

    Add action_cable_meta_tag in the correct position:

    <!DOCTYPE html>
    <html>
      <head>
        <title>Chat</title>
        <%= csrf_meta_tags %>
        <%= action_cable_meta_tag %>
    
        <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true %>
        <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
      </head>
    
      <body>
        <%= yield %>
      </body>
    </html>
    
  2. app/assets/javascripts/cable.coffee:

     @App ||= {}
     App.cable = ActionCable.createConsumer()
    
  3. app/assets/javascripts/channels/subscriptions/alert.coffee:

    App.cable.subscriptions.create "AlertsChannel",
      connected: ->
        # Called when the subscription is ready for use on the server
    
      disconnected: ->
        # Called when the subscription has been terminated by the server
    
      # Called when there's incoming data on the websocket for this channel
      received: (data) ->
        $('#navbar_alert').html( data['alert'] )
    
  4. config/routes.rb:

    Rails.application.routes.draw do
      ...
      # Serve websocket cable requests in-process
      mount ActionCable.server => '/cable'
      ...
    end
    
  5. config/cable.yml:

    This moved recently from config/redis/cable.yml

    # Action Cable uses Redis to administer connections, channels, and sending/receiving messages over the WebSocket.
    production:
      url: redis://localhost:6379/1
    
    development:
      url: redis://localhost:6379/2
    
    test:
      url: redis://localhost:6379/3
    
  6. app/channels/application_cable/channel.rb:

    module ApplicationCable
      class Channel < ActionCable::Channel::Base
      end
    end
    
  7. app/channels/application_cable/alerts_channel.rb:

class AlertsChannel < ApplicationCable::Channel
  def subscribed
    # send to the user_id named broadcasting
    stream_from "alerts_channel_#{current_user.id}"
  end

  def unsubscribed
  end
end
  1. app/channels/application_cable/connection.rb:
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    include SessionsHelper
    identified_by :current_user

    def connect
      self.current_user = find_verified_user
    end

    protected
      def find_verified_user
        if current_user = user_from_remember_user_token
          current_user
        else
          reject_unauthorized_connection
        end
      end
  end
end
  1. app/models/alert.rb:
class Alert < ApplicationRecord
  ...
  after_create_commit { AlertBroadcastJob.perform_async self }
  ...
end
  1. app/jobs/alert_broadcast_job.rb:
class AlertBroadcastJob < ApplicationJob

  def perform( alert )
    # resend all of the alerts
    alerts = Alert.where( user_id: alert.user.id ).order( :name )
    # send to the user_id named broadcasting
    user_id = alert.user.id
    ActionCable.server.broadcast "alerts_channel_#{user_id}",
       alert: render_alert( alerts )
  end

  private
    def render_alert( alerts )
      ApplicationController.renderer.render( partial: 'alerts/alert', locals: { alerts: alerts } )
    end
end
  1. config.ru
# This file is used by Rack-based servers to start the application.
require ::File.expand_path('../config/environment', __FILE__)

# Action Cable uses EventMachine which requires that all classes are loaded in advance
Rails.application.eager_load!
#require 'action_cable/process/logging'

ActionCable.server.config.disable_request_forgery_protection = true

run Rails.application

© 2020 Keith P | Follow on Twitter | Git