Getting Started with Action Cable

Responding to an action on Active Record

Apple Pie Lattice Dessert by Tarina is licensed under the Creative Commons Zero Licence

With the inclusion of Action Cable in Rails 5, we can easily add WebSockets to our Rails applications. I recently ran into a situation where I needed to run a background job, I’ll call it BakeFoodJob, with an Active Record object, Pie. When the pie was done baking, I needed to update the page so the user could see that it was done. This proved to be an opportune place to use WebSockets.

Rails has a generator to get us started, so we will use that to get us our base files for our Bakery channel. The generator also takes an optional list of additional channel actions, but we won’t need that here.

$ rails generate channel Bakery

We can now start setting up our channel. Let’s set up a stream for our pie. The stream is what will route the published messages to the subscriber we will have on the page listening for messages.

app/channels/bakerychannel.rb

class BakeryChannel < ApplicationCable::Channel
  def subscribed
    pie = Pie.find(params[:id])
    stream_for pie
  end
end

We also want to make sure we are sending messages to this channel. We can do that by using #broadcast_to. I want to update when the job is finished, so I will put it at the end of #perform. You should put your broadcast at whatever point you want to update the page with some information.

app/job/bake_food_job.rb

class BakeFoodJob < ApplicationJob
  def perform
    # Process job
    BakeryChannel.broadcast_to(pie, {})
  end
end

Because I want access to pie.id in the JavaScript, I’ll make sure we have it on the page for us to find.

<div data-pie-id="<%= @pie.id %>"></div>

We will subscribe to and act on the incoming messages with JavaScript. The generated subscription will have the channel name and a hash. To include more information now we’re going to change the channel name to a hash that has both that name and the id.

app/assets/javascripts/channels/bakery.js

$(document).on("ready", function() {
    App.bakery = App.cable.subscriptions.create({
    channel: "BakeryChannel",
    id: $("[data-pie-id]").data("pie-id")},
    {
    received: function(data) {
      // Called when there's incoming data on the WebSocket for this channel.
      // Update the page with the newest broadcast data.
      alert("Pie is done!");
    },
  });
});

We only want to subscribe to the channel once, when the page has loaded. Because we are using
links, we need to set up our subscription when Turbolinks is loaded. In addition, we don’t want to end up with duplicate subscriptions, so we will remove any old ones.

app/assets/javascripts/channels/bakery.js

$(document).on("ready turbolinks:load", function() {
  var pieId = $("[data-pie-id]");
  if (pieId) {
    if (App.bakery) {
      App.cable.subscriptions.remove(App.bakery);
    }
    App.bakery = App.cable.subscriptions.create({
        channel: "BakeryChannel",
        id: $("[data-pie-id]").data("pie-id")},
    {
      received: function(data) {
        alert("Pie is done!");
      },
    });
  }
});

Now, all that’s left is the configuration. If you have an ENV["HOST"] in the format example.com, you can use this in your production.rb.

config.action_cable.url = "wss://#{ENV["HOST"]}/cable"
config.action_cable.allowed_request_origins = ["https://#{ENV["HOST"]}"]

Action Cable comes with a few adapters built in. Many tutorials will use Redis, but because I’m already using PostgreSQL, I’ll use that adapter.

config/cable.yml

development:
  adapter: postgresql
test:
  adapter: postgresql
production:
  adapter: postgresql

That’s it! That’s all you need to do to start working with Action Cable.

Photo of Victoria Gonda

Victoria is a software developer working on mobile and full stack web applications. She enjoys exchanging knowledge through conference talks and writing.

Comments

  1. Michael
    February 07, 2019 at 5:24 AM

    Great and simple analogy to teach this concept. Also, thank you for going into the ‘jobs’ portion of ActionCable.