ActionCable: A Simple Example


Rails 5 is released a few days ago, and ActionCable is one of the most notable features. The following is a very simple but workable example.

Installation

Add gems to the Gemfile:

# Rails must be newer than 5.0
gem 'rails', '>= 5.0.0'

# ActionCable uses redis to store and retrieve data
gem 'redis'

# A server for websocket connection
gem 'puma' 

In addition, Redis requires configurations in config/cable.yml. It uses async as default in development environment. In other words, it requires redis only in production environment. If you want to use redis locally, make sure it is installed by running:

brew install redis

Or sudo apt get install redis-server if you're on a Ubuntu machine. After that, run:

redis-server

in order to run the server on port 6379.

Server Side Setup

This demo will not use any model. Instead, it simply allows you to establish a websocket connection, and from which you will know what to do next. If you don't know how websocket works, it establishes a HTTP connection from the client to the server, and upgrades that connection to websocket afterwards. Thus it does not need to reconnect anymore like using AJAX.

If you're upgrading from Rails 4, it is necessary to have app/channels/application_cable and two files in it. Follow this example directory on GitHub and copy the files if you need it.

Before defining any channel, we mount ActionCable to a specific route in config/routes.rb:

Rails.application.routes.draw do
  mount ActionCable.server => '/channels'
end

And add the following to the config/environments/development.rb:

Rails.application.configure do 
  config.action_cable.url = "ws://localhost:3000/cable"
end 

Apparently you will have to do the same in production environment before deployment. And add the meta tags in views/layouts/application.html.erb:

<%= action_cable_meta_tag %>

Skip this step if you're hosting an API server.

Now let's create a new channel file app/channels/messages_channel.rb as if we're creating a real-time messaging app. It goes like this:

class MessagesChannel < ApplicationCable::Channel
  def subscribed
    stream_from 'MessageChannel'
  end

  def receive
    ActionCable.server.broadcast('MessageChannel', message: 'message received!', head: :ok)
  end
end
  • The subscribed method defines the channel name that we're subscribing to from the browser.
  • The receive method is called when this channel receives any message from the browser.
  • The ActionCable.server.broadcast method is for sending messages to the browser through its subscription. The message attribute can by any attribute.

Also, we have to create a page for testing the connection. Create a app/controllers/messages_controller.rb:

Rails.application.configure do
  get "/" => "messages#index"
end

Add app/views/messages/index.html.erb:

<button id="subscribe">Subscribe</button>
<button id="send">Send</button>

That's it!

Client Side Setup

Most tutorials have gone through the client-side setup. However, this tutorial is not using any JavaScript library from ActionCable. We're establishing the connection from scratch. First, let's take a look at the app/assets/javascripts/application.js and see there's a basic setup for our application:

(function() {
  this.App || (this.App = {});
  App.cable = ActionCable.createConsumer();
}).call(this);

Remove these lines and add:

$(document).ready(function(){

  var ws = new WebSocket("ws://localhost:3000/channels");

  $("#subscribe").click(function(){
    ws.send(JSON.stringify({
      command: "subscribe",
      identifier: "{\"channel\":\"MatchesChannel\"}"
    }));
  });

  $("#send").click(function(){
    ws.send(JSON.stringify({
      command: "message",
      identifier: "{\"channel\":\"MatchesChannel\"}",
      data: JSON.stringify({'foo': 'bar'})
    }));
  });
});