Skip to content

Rodrigo Urubatan – About Code

Helping ruby developers to use the best tools for each job so they can solve hard problems, with less bugs and have more free time.

Menu
  • Home
  • My last presentations
  • About
  • Privacy Policy
Menu

Quick and Dirty introduction to ActionCable – the best WebSockets for Rails!

Posted on 2018-05-16 by Rodrigo Urubatan

This post is a followup and a translation of my presentation from “The Developers Conference Florianopolis 2018”

What are WebSockets good for?

  • Update the screen of many clients simultaneously when the database is updated
  • Allow many users to edit the same resource at the same time
  • Notify users that something happened

Among many other things.

I’ll not try to convince you that websockets are the best solution for these, and of course you have many options to use, for example:

  • Node.js
  • Websocket-rails
  • ActionCable

I’ll focus here in how to easily use ActionCable that is the default rails implementation and it made my life a lot easier in the last few months (I used websocket-rails before but it’s not being actively developed for a long time now…)

ActionCable basics

Besides being an awesome and simple API, ActionCable has one excellent performance (according to my tests) and has a really good connection handling.

ActionCable is a pub/sub implementation, and that makes things a lot simpler, and to simplify the pub/sub implementation it uses channels.

Each client connection connects to a channel in the server, each channel implementation, streams to a named channel defined when the client connects, allowing to use parameters to define the channel name.

Then the server can send back messages to any of the defined named channels.

Ok, writing it like that, it seems kinda complicated, but it is really simple.

For example, if you wanna send from Ruby a notification to any client, you’ll send data to one of these named channels, with a code similar to this:

ActionCable.server.broadcast 'broadcast_sample', data

where “broadcast_sample” is the name of a channel, and data is any object, for me, usually a hash with the information I want to send back to the clients.

Of course you need to define the name of the channel when the users connect, and this is done in the “ActionCable::Channel” instances in the “subscribed” method, like in the sample bellow:

class MyChannel < ApplicationCable::Channel
  def subscribed
    stream_from "broadcast_sample"
    stream_from "nome#{params[:name]}"
    stream_for current_user
  end
  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end
end

As you can see above, from that method, it is possible to define a constant name for a topic/channel, use parameters sent by the user to define the name, and you can use the “model” variant, that is just a shortcut for creating a string name for that model.

The key is to use the “stream_from” or “stream_for” methods and use the same name later in the broadcast name.

Just to make it clearer how to send a broadcast to each of these 3 samples above, I’ll show bellow a sample code for each:

ActionCable.server.broadcast 'broadcast_sample', data

ActionCable.server.broadcast ‘nomeRodrigo’, comment: ‘Teste’, from_id: 47

ActionCable.server.broadcast_to @post, @comment

Receiving messages in Javascript

Ok, but how do you receive these messages in Javascript? it is almost as easy, just need to implement the “received” method like in the sample bellow:

App.bcsample = App.cable.subscriptions.create("BcsampleChannel", {
    connected: function () {
        // Called when the subscription is ready for use on the server
    },

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

    received: function (data) {
        // Called when there's incoming data on the websocket for this channel
        var message = $("<div/>");
        message.text(data.message);
        $('.message-list').append(message);
    },

    speak_to_all: function (message) {
        return this.perform('speak_to_all', {user_id: window.name, message: message});
    }
});

Important points in this sample:

  • BcsampleChannel is the class name of the channel in Ruby
  • the data parameter in the received function is the data passet to the broadcast function, it should always be an object, a string does not works, I’ve tried it.

And how to call ruby code from javascript?

Just take a look at the last part of the sample above, in the “speak_to_all” method, the “perform” method, will call a  method with the same name, passing the hash parameter as the data parameter to a method “speak_to_all” in the “BcsampleChannel” class.

Of course we need to update that class to receive this call, like in the sample bellow:

class BcsampleChannel < ApplicationCable::Channel
  def subscribed
    stream_from "broadcast_sample"
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end

  def speak_to_all(data)
    ActionCable.server.broadcast 'broadcast_sample', data
  end
end

This sample, will receive any data and broadcast it to all connected clients.

There is one last question, how do we pass parameters to subscribed method? simple, just take a quick look at the sample bellow:

App.privatesample = App.cable.subscriptions.create({channel:"PrivatesampleChannel", windowid: window.name}, {
  connected: function() {
    // Called when the subscription is ready for use on the server
  },

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

  received: function(data) {
    // Called when there's incoming data on the websocket for this channel
  },
});

in the create method, instead of passing the name as a string, we need to pass an object, and the “channel” property is required, anything else will be a parameter to the channel in Ruby to use as needed.

But how about deploying?

  • You can use Redis or a database as a backend
  • If you are using passenger and nginx your are almost done!
  • Remember to setup the server path in the routes.rb
  • test and be happy

The first step is to edit the “config/cable.yml” file like the sample bellow:

production:
  adapter: redis
  url: redis://redis.example.com:6379

local: &local
  adapter: redis
  url: redis://localhost:6379

development: *local
test: *local

Then you need to add the mapping to the “config/routes.rb” file:

# Serve websocket cable requests in-process
mount ActionCable.server => '/cable'

and just add a location config to your nginx configuration like in the host bellow:

server {
    listen 80;
    server_name www.foo.com;
    root /path-to-your-app/public;
    passenger_enabled on;

    ### INSERT THIS!!! ###
    location /cable {
        passenger_app_group_name YOUR_APP_NAME_HERE_action_cable;
        passenger_force_max_concurrent_requests_per_process 0;
    }
}

Of course you have the option to start the server as a standalone server, and configure the reverse proxy, but that is a subject to another post.

You can send broadcasts to it from a sidekiq job or from rails console, as soon as you do not forget to configure the backend as shown above.

And if you have problems or questions about using or deploying ActionCable please leave a comment bellow, I’ll answer as fast as possible.

 

Related

2 thoughts on “Quick and Dirty introduction to ActionCable – the best WebSockets for Rails!”

  1. Terry says:
    2018-10-24 at 13:46

    What are the benefits of using standalone server? Could you explain it in context of containerized rails app? Thank you.

    Reply
    1. urubatan says:
      2019-01-03 at 11:30

      Hi Terry,
      if you are deploying a containerized app, a standalone server is not a good approach in my opinion, currently I deploy my rails apps as containers and using passenger is also not a good approach.

      You can deploy the app to the container using a ruby server, and add a front proxy using nginx for example (this is my current setup)

      And nginx will proxy both the HTTP and WS requests.

      Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Recent posts

  • Why Embrace Nesting in Ruby Modules?
  • An easy way to have a local “Github Copilot” for free
  • SPA without touching Javascript – The magic of Ruby on rails and Hotwire
  • I see Dead Jobs everywhere (sidekiq DeadSet)
  • Quick tips that help: rails notes

Arquives

  • May 2024
  • April 2024
  • February 2023
  • January 2023
  • December 2022
  • June 2021
  • March 2020
  • January 2020
  • July 2019
  • June 2019
  • May 2019
  • October 2018
  • September 2018
  • August 2018
  • July 2018
  • June 2018
  • May 2018
  • February 2018
  • January 2018
  • November 2017
  • August 2015
  • August 2014
  • July 2014
  • August 2007

Categories

  • AI
  • articles
  • cfp
  • firebase
  • gems
  • git
  • opinion
  • presentations
  • projects
  • rails6
  • ruby
  • Sem categoria
  • server-api
  • tutorials
  • Uncategorized
© 2025 Rodrigo Urubatan – About Code | Powered by Minimalist Blog WordPress Theme