ActionCable
Integrated WebSockets for Rails
Ruby Talks #9
Szubrycht Kamil
Text
https://www.zweitag.de/en/blog/technology/lets-build-a-chat-with-actioncable
Architecture
ActionCable Server
# config/routes.rb
Rails.application.routes.draw do
( ... )
# Serve websocket cable requests in-process
mount ActionCable.server => '/cable'
end
In app
ActionCable Server
# cable/config.ru
require ::File.expand_path('../../config/environment', __FILE__)
Rails.application.eager_load!
require 'action_cable/process/logging'
run ActionCable.server
$ bundle exec puma -p 28080 cable/config.ru
Standalone
Redis
# 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
ActionCable endpoint configuration
# config/environments/production.rb
config.action_cable.url = "ws://example.com:28080"
config.action_cable.allowed_request_origins = [ 'http://localhost:3000' ]
# config.action_cable.disable_request_forgery_protection = true
Establish the connection
# app/assets/javascripts/cable.coffee
( ... )
@App ||= {}
App.cable = ActionCable.createConsumer()
( ... )
Authorization of incoming connections
# app/channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
if cookies.signed[:username].blank?
reject_unauthorized_connection
else
self.current_user = cookies.signed[:username]
end
end
end
end
Client-side
App.chat = App.cable.subscriptions.create "ChatChannel",
connected: ->
$('#message-input').show()
$('#connection-error, #connect-button').hide()
disconnected: ->
$('#connection-error').show()
$('#message-input, #connect-button').hide()
received: (data) ->
$('#messages').append(data.message)
speak: (msg) ->
@perform 'speak', message: msg
Server-side
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from 'admin_messages' if current_user.start_with?('admin')
stream_from 'messages'
end
def speak(data)
stream = current_user.start_with?('admin') ? 'admin_messages' : 'messages'
ActionCable.server.broadcast(stream, message: render_message(data['message']))
end
private
def render_message(message)
ApplicationController.render( partial: 'messages/message',
locals: {
message: message,
username: current_user }
)
end
end
Summary
- very easy to work with WebSockets
- client-side and server-side solution
- access to full domain model
- each channel can be streaming zero or more broadcastings
RT#9: ActionCable
By Kamil Szubrycht
RT#9: ActionCable
- 691