Connect an Autonomous JS App to ActionCable for RealtimeÂ Goodness
Tags: ruby, rails, actioncable, javascript
Published: 2017-08-16 14:29:27

---
title: Connect an Autonomous JS App to ActionCable for RealtimeÂ Goodness
published: true
description: A brief tutorial explaining one way to utilize Ruby on Rails' ActionCable from a standalone JS application.
tags: ruby, rails, actioncable, javascript
---

<img src="https://thepracticaldev.s3.amazonaws.com/i/e324ir7ndltqmb9rm56c.jpg" style="width:100%;"/>

A few months ago I started learning Rails Action Cable for Fixt so that I could implement desktop notifications for our customer service reps. For those of you who don't know about Action Cable, it's an out-of-the-box websockets framework built for (and now into) rails. This was pretty straight forward as there are plenty examples in Ruby on Rails' preferred coffeescript, connecting via the asset pipeline. But as someone with somewhat of a shallow knowledge of websocket trends, I began to ask myself what if you want to connect from another standalone web app? Finally I had to face this  question as I was tasked with connecting our [Repair Tracking Component]() to our Ruby on Rails backend for real time tracking. This is where I started to explore the best way to utilize Action Cable from any Javascript app.

<figure>
  <img src="https://thepracticaldev.s3.amazonaws.com/i/moqu48lfjlr8jnxdav3e.gif">

  <figcaption style="color:gray;font-size:11px;">
    <a href="https://fixt.co/repairs">Submit a phone or tablet repair</a> with Fixt to see the full feature for yourselfÂ ;)
  </figcaption>
</figure>

------------------------

### Action Cable Setup
This isn't going to be a full tutorial, meaning I won't include all of the setup of action cables because this has been done well by [the Edge Guides](http://edgeguides.rubyonrails.org/action_cable_overview.html) already. Instead I will focus on the changes you need to make to your configuration if you already have the standard action cable setup.

You'll need to do two things to make sure that you can connect to your websocket from an outside source. First you'll need to add your client's address to the list of allowed origins.

```rb
# config/environments/development.rb
config.action_cable.url = 'http://localhost:3000/cable'
config.web_socket_server_url = 'ws://localhost:3000/cable'
config.action_cable.allowed_request_origins = [
   # Local address of our RoR server
  'http://localhost:3000',
   # Local address we use for our React standalone client
  'http://localhost:8000',
]
```

```rb
# config/environments/production.rb
config.websocket_server_url = 'wss://<YOUR_SERVER_SITE>/cable'
config.action_cable.allowed_request_origins = [
   # Address of our Ruby on Rails App
  'https://<YOUR_SERVER_SITE>',
   # Address of our JS App
  'https://<YOUR_CLIENT_SITE>',
]
```
_Note: I'm assuming that you're using ssl certificates in production, but if you're not, then just change `https` to `http` and `wss` to `ws`_

If you haven't already, you'll need to setup a channel to track whichever model that you want to receive updates about. There are two paradigms in Action Cable, you can either stream for an Active Record object or you can stream to a channel in general. The differences are better explained [in the Edge Guides Section 5.1 Streams](http://edgeguides.rubyonrails.org/action_cable_overview.html#streams). For the sake of simplicity, I'll explain what we want to do. We essentially want to stream any updates, to an instance of a model, to any client that's interested in that model. In our case at Fixt, we wanted to be able to track updates to a specific Repair instance. 

```rb
# app/channels/repair_tracking_channel.rb
class RepairTrackingChannel < ApplicationCable::Channel
  def subscribed
    stream_for repair
  end
  
  def repair
    Repair.find(params[:id])
  end
end
```

Now, any time that we want to update the client that's interested in the repair when something changes, all we have to do is call something like this:

```rb
RepairTrackingChannel.broadcast_to(@repair, repair: repair.as_json)
```

_Note: You don't have to use `as_json`. We actually use jbuilder at Fixt but since this article isn't about serializing data, I didn't want to spend too much time on it._

------------------------

### Javascript Setup
Now that we have Action Cable configured to stream to our standalone client JS app, let's set the client up. Everything before now has been vanilla Action Cable so this next part is the fun part.

Action Cable is just a layer on top of web sockets so you really can connect to it just using good ole JS web sockets. For this article, I'm just going to use the `actioncable` npm package because it makes the boilerplate web socket code a little easier to understand. If you are an anti-dependency, hardcore, 100x brogrammer who thinks npm is for the weak, then you probably don't need this tutorial or anyone's help, because you are unequivocally intelligent and we will all clap as you exit this thread. 

<figure>
  <img src="https://thepracticaldev.s3.amazonaws.com/i/6j0fw0kkh1ivujwlqtn4.gif">
  <figcaption style="color:gray;font-size:11px">
     Not you Hermione, you're perfect. <3
  </figcaption>
</figure>


Let's go ahead and install actioncable to our project.

```bash
$ npm i -S actioncable
```

Then, let's create a file called `repair-tracking-subscription.js`
```bash
$ touch repair-tracking-subscription.js
```

With this file, we want to encapsulate all of our Action Cable channel logic, similar to how you would if you were connecting to ActionCable via the asset pipeline.

```js
import ActionCable from 'actioncable';

// 1. Configure your websocket address
const WEBSOCKET_HOST = process.env.NODE_ENV === 'production' 
                         ? 'wss://<YOUR_SERVER_SITE>/cable' 
                         : 'ws://localhost:3000/cable';
                         
export default function RepairTrackingSubscription(
  repairId, 
  { onUpdate = () => {} } = {}
) {
  // 2. Define our constructor
  this.cable = ActionCable.createConsumer(WEBSOCKET_HOST);
  this.channel;
  this.repairId = repairId;
  this.onUpdate = onUpdate;
  
  // 3. Define the function we will call to subscribe to our channel
  this.subscribe = () => {
    this.channel = this.cable.subscriptions.create(
      { channel: 'RepairTrackingChannel', id: this.repairId },
      {
        connected: this.connected,
        disconnected: this.disconnected,
        received: this.received,
        rejected: this.rejected,
      }
    );
  };
  
  // 4. Define our default ActionCable callbacks.
  this.received = (data) => {
    console.log(`Received Data: ${data}`);
    
    this.onUpdate(data);
  };
  
  this.connected = () => {
    console.log(`Tracking Repair ${id}`);
  };
  
  this.disconnected = () => {
    console.warn(`Repair Tracking for ${id} was disconnected.`);
  };
  
  this.rejected = () => {
    console.warn('I was rejected! :(');
  };
}
```

1. This will be the `ws`/`wss` address that you set up in the previous section. You don't have to hardcode it in here but I am not going to presume to know your environment.
2. For those unfamiliar with javascript functions and object oriented programming, this is our constructor and anything starting with `this` is a member variable on our object.
3. We use the `subscribe` function to essentially invoke our call to our ActionCable server. You could do this in the constructor and save a step but I figured it's worth separating for understanding. Also this way would allow you to pass around a subscription and subscribe at your will.
4. These are your ActionCable callbacks that are invoked by default when certain actions occur from the channel. You can read more about default and custom callback functions on the [Edge Guides 5.3 Subscriptions](http://edgeguides.rubyonrails.org/action_cable_overview.html#client-server-interactions-subscriptions).

That's it! Now we can track a repair from anywhere. Just import this function and subscribe like so:

```js
import React, { Component } from 'react';
import repairTrackingSubscription from './repair-tracking-subscription';

class Tracker extends Component {
  state = {
    repair: {},
  };

  componentWillMount() {
    const { repairId } = this.props;
    
    const repairChannel = new RepairTrackingSubscription({ 
      repairId, 
      onUpdate: this.onRepairUpdate, 
    });
    
    repairChannel.subscribe();
  }
  
  onRepairUpdate = (data) => {
    const { repair } = data;
    this.setState(() => ({ repair }));
  }
  
  render() {
    const { repair } = this.state;
    
    return (
      <div>
        { JSON.stringify(repair) }
      </div>
    );
  }
}

export default Tracker;
```
_Note: This is framework agnostic, you can use this function in any javascript situation that you currently find yourself in. I just currently find myself in React and can't seem to get out. Send help_

## Conclusion
This may seem simple to many of you but I legitimately wasn't sure how to connect to Action Cable from an autonomous app last week. Most of the tutorials out there made the assumption that you'd be working from within the Ruby on Rails framework. I hope that this helps some of you make some cool ish.