You can visit the Part 1 here.

THE RAILS WAY…

When you create a new rails 5 application, rails generates some files for you:

For any implementation of a websocket connection, we need both the client and the server parts of the code.

CLIENT SIDE

For the client side Rails providesÂ app/assets/javascripts/cable.js which loads action_cable js and all files in channels directory.

On page load, a consumer is created and exposed via App.cable. If we would go a little bit into the client side code for action_cable, we would find that rails does all the heavy loading like instantiating subscriptions and connections…monitoring connections etc. pretty cool.. right ? function Consumer (url) { this .url = url; this .subscriptions = new ActionCable.Subscriptions( this ); this .connection = new ActionCable.Connection( this ); }

For most practical purposes, you would not be modifying anything in this file.

SERVER SIDE

Actually, rails generates an empty Connection class.

However, for almost all practical applications, we would need some sort of authorization on the incoming connections.

Here’s a good tutorial on ActionCable devise authentication

module ApplicationCable class Connection < ActionCable::Connection :: Base identified_by :current_user def connect self .current_user = find_verified_user logger.add_tags 'ActionCable' , current_user.name end protected def find_verified_user if verified_user = User .find_by( id: cookies.signed[ :user_id ]) verified_user else reject_unauthorized_connection end end end end

Here, identified_by is a connection identifier.

Therefore, we can use it to retrieve, and thereby disconnect, all open connections for a given user.

is a connection identifier. Therefore, we can use it to retrieve, and thereby disconnect, all open connections for a given user. If you implement connect method, the same will be called while handling a websocket open request.

method, the same will be called while handling a websocket open request. You can call reject_unauthorized_access if you don’t want the current_user to connect

The app/channels/application_cable/channel.rb contains your ApplicationCable::Channel where you put shared logic forÂ your channels.

It’sÂ similar to ApplicationController for controllers.

All that came right out of the box.

Let’s go ahead and implement a common use case… The Chat Application.

For a chat application, we would have these three basic requirements:

We should be able to subscribe to a channel,

Publish something on that channel

Receive the published message on the subscribed channel.

THE channel GENERATOR

Rails 5 provides a new channel generator which creates two new files.

One ruby file and one js file.

This generator is similar to the familiar controller generator. You specify the name of the channel (room) and one or more public methods which can be invoked as Remote Procedures ( we’ll come to it in a while )

Let’s see what we have in each of these files for this particular example..

CLIENT SIDE JS CODE

# app/assets/javascripts/channels/room.coffee App.room = App.cable.subscriptions. create "RoomChannel" , connected: -> # Called when the subscription is ready for use on the server disconnected: -> # Called when the subscription has been terminated by the server received: (data) -> # Called when there 's incoming data on the websocket for this channel speak: -> @perform ' speak '

Rails created a subscription for the RoomChannel . Please note that the name is exactly same as the name of the class that we have for the channel. Then it provides empty implementations for three callbacks: connected, disconnected and received.

Then we have a speak method which basically invokes the perform method with the string speak as its argument. again, that name is important.

We’ll come to it later that why this naming is important. But the good thing is rails did all that for us and we don’t need to worry about it unless we override the defaults.

SERVER SIDE RUBY CODE

class RoomChannel < ApplicationCable::Channel def subscribed end def unsubscribed end def speak end end

For the channel class in ruby, We have empty implementations for two callbacks, subscribed and unsubscribed.

Also, we have an empty implementation for our speak method.

I think you would appreciate, that we have all this structure ready and we have only typed one or two generators so far.

DEMO FROM DHH

Here‘s the famous action cable demo from DHH, we’ll use snippets from there to understand what each portion of code does.

SERVER SIDE CODE

class RoomChannel < ApplicationCable::Channel def subscribed stream_from "room_channel" end def speak (data) Message .create! content: data[ 'message' ] end end

The subscribed callback is called whenever a new connection is opened i.e. when you open a new tab.

There’s a corresponding unsubscribed callback as well which is invoked when a connection is closed.

callback is called whenever a new connection is opened i.e. when you open a new tab. There’s a corresponding callback as well which is invoked when a connection is closed. The stream_from method is called with the name of the broadcasting pubsub queue (‘room_channel’ in this case). This name is important and should be same as the one on which the broadcast is invoked.

The speak method would be invoked as a remote procedure from the client. Here, we are just creating a new message with the passed args. You may broadcast from here itself if you want to. However, for all practical applications,Â we might have a large number of subscribers and it would make sense to handle it asynchronously in a delayed job.

class Message < ApplicationRecord after_create_commit :broadcast_self private def broadcast_self MessageBroadcastJob .perform_later( self ) end end

And here’s the code for the message broadcast job.

class MessageBroadcastJob < ApplicationJob queue_as :default def perform (message) ActionCable .server.broadcast 'room_channel' , message: render_message(message) end private def render_message (message) ApplicationController .renderer .render( partial: 'messages/message' , locals: { message: message }) end end

Notice how we invoke the broadcast on a given named pubsub queue ( the one we passed as an argument to stream_from ) with the hash that we want to broadcast.

CLIENT SIDE CODE

Here’s a JS equivalent of the CoffeeScript that was used in the demo.

( function () { App.room = App.cable.subscriptions.create( "RoomChannel" , { received: function (data) { $( '#messages' ).append(data.message) }, speak: function (message) { return this .perform( 'speak' , { message: message }); } }); $(document).on( 'keypress' , '[data-behavior="room_speaker"]' , function (event) { if (event.keyCode === 13 ) { App.room.speak(event.target.value); event.target.value = '' event.preventDefault(); } }); }).call( this );

I think this snippet explains why the exact string ‘speak’ was important. That’s becauseÂ the server side ruby instance has exposed this method and this can be invoked as a Remote procedural call over the WebSocket connection.

RUNNING THIS DEMO TO EXPLORE LOGS

Here’s the animation showing the working of the demo app.

WHEN A CLIENT CONNECTS

Initial handshake and upgrade of HTTP to WebSocket

The client subscribes to a channel

The server logs also show the initial HTTP upgrade along with the subscription to the channel.

WHEN A CLIENT MAKES AN RPC AND MESSAGE BROADCASTS.

The client invokes the channel’s speak method which in turn results in a broadcast along the channel as seen in the returned frame.

The server logs show the invocation of RoomChannel#speak followed by its persistence in db and broadcast along the channel.

SECURING AGAINST CROSS SITE WEBSOCKET HIJACKING

The websockets support cross domain requests…which means it is also vulnerable to the security threats which result due to this behavior.

Here‘s more info on this topic.

Rails does all the heavy lifting for you… and all you need to do is some configurations and you are good to go… Action cable only allows requests from origins configured by action_cable.allowed_request_origins in your config file.

in your config file. In case of development, this defaults to “http://localhost:3000“

You can configure to turn off this check using disable_request_forgery_protection in your config file.

Rails.application.config .action_cable .allowed_request_origins = [ 'http://rubyonrails.com' , /http:\/\/ruby.*/ ] # You can disable this check by : # Rails.application.config.action_cable.disable_request_forgery_protection = true

Message queues via redis… but I don’t need them in development…

Yes… the rails community also thought so.

And if you see in the config/cable.yml , you’ll see the adapter is async instead of redis.

which means ?

In your development mode, the message queue is maintained in memory and will be lost once the server is shut down (which is the desired behavior in most development scenarios).

However, if you want, you may use a redis server as well by uncommenting the redis gem in the gem file and configuring config/cable.yml

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

In production, Action Cable uses Redis by default to administer connections, channels, and sending/receiving messages over the WebSocket.

So that was a brief intro to the ActionCable which fills in the gap for real-time features in Rails.

Hope you found it interesting. Thanks for reading.