Powering your Ember apps with Rails' ActionCable

Posted on

ActionCable is an upcoming component of Ruby on Rails 5.0 - it is the websockets framework wich aims to simplify the addition of realtime features. In this post, we'll explain the integration between ember.js and actioncable through a simple example app: a very basic chat where people can pick a username and start posting to a public chatroom. We'll be introducing the new ember-cable addon, which makes it dead-simple to add ActionCable's power to your Ember apps.

ActionCable works also with Rails 4.2 and we will use ember-cli-rails to integrate ember into rails. Please follow our previous tutorial on how to setup ember and rails

This tutorial will take you 30 minutes. If you are too busy to read how to build it from scratch, jump to this repository with the complete code.

Setting up threaded server

In this example we will use puma, a simple, fast, threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications. First, make sure Puma is in your Gemfile

gem 'puma'

then install dependencies

$ bundle install

and run the server to check that everything's working.

$ rails s
=> Booting Puma
=> Rails 4.2.5 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
Puma 2.15.3 starting...
* Min threads: 0, max threads: 16
* Environment: development
* Listening on tcp://localhost:3000

step 1

Setting up ActionCable

Now that we have everything else in place, let’s set up ActionCable. Add the actioncable gem into Gemfile

gem 'actioncable', github: 'rails/actioncable'

then install dependencies

$ bundle install

Now we need to extend ApplicationCable::Connection and ApplicationCable::Channel - the latter will be the superclass for our own channel classes. Rails will hopefully generate these stubs for us, once the final version is released.

# app/channels/application_cable/connection.rb
module ApplicationCable
  class Connection < ActionCable::Connection::Base
  end
end
# app/channels/application_cable/channel.rb
module ApplicationCable
  class Channel < ActionCable::Channel::Base
  end
end

ActionCable uses Redis for publishing and subscribing to channels, so we need to configure it.

# config/redis/cable.yml
local: &local
  :url: redis://localhost:6379
  :host: localhost
  :port: 6379
  :timeout: 1
  :inline: true
development: *local
test: *local

The last step to complete ActionCable setup on backend is routing configuration.

# config/route.rb
Rails.application.routes.draw do
  # keep this on top
  match '/websocket', to: ActionCable.server, via: [:get, :post]

  mount_ember_app :frontend, to: "/"
end

Create backend channel

We need to create a channel in order to manage subscriptions:

# app/channels/messages_channel.rb
class MessagesChannel < ApplicationCable::Channel
  def subscribed
    stream_from 'messages'
  end

  def receive(data)
    ActionCable.server.broadcast 'messages', data
  end
end

Setting up ember-cable

The next step is to add ember-cable to our ember application. Following the documentation let's install it with:

ember install ember-cable

and running npm install command.

Create chat component

Now that we have a support for ember cable in both backend and frontend, we can create a simple component to test it all together. Change to frontend directory and type:

ember g component simple-chat

app/components/simple-chat.js

import Ember from 'ember';

export default Ember.Component.extend({
  cableService: Ember.inject.service('cable'),

  messages: [],
  username: 'guest',
  body: 'message body',

  setupSubscription: Ember.on('init', function() {
    var consumer = this.get('cableService').createConsumer('ws://localhost:3000/websocket');

    var subscription = consumer.subscriptions.create("MessagesChannel", {
      received: (data) => {
        this.get('messages').pushObject({username: data.username, body: data.body});
      }
    });

    this.set('subscription', subscription);

  }),

  actions: {
    sendMessage() {
      this.get('subscription').send({ username: this.get('username'), body: this.get('body') });
    }
  }
});


app/templates/components/simple-chat.hbs

{{input value=username}}
{{input value=body }}
<button {{action "sendMessage"}}>
  Send
</button>

{{#each messages as |message|}}
  <p>{{message.username}} -> {{message.body}}</p>
{{/each}}

Let's chat!

Now that you have a chat component, why not try and use it? Just add it to application.hbs template, then start the app, open it in many browser windows and enjoy chatting!

final

I hope I’ve peaked your interest with this article. The code used in this article is available here. Feel free to join the discussion in the comments.