Interactivity. Every web app needs it.
And Rails comes with a number of tools, which, together, generate that feeling of a fast, responsive app, with mostly server rendered HTML and a little Javascript sprinkled on top. ActiveJob fetches data from a remote JSON api in the background. ActionCable routes data from the background job out to the front end, and Stimulus.js puts the new data right into place.
Here is a tutorial that wraps these three pieces together. It will pull the recent Rails Repository tags from Github and display them. It will then let someone select the tag, and load the commits using a previous tutorial’s Stimulus controller. It even has a challenge assignment at the end for a little extra practice.
Background Service Jobs
ActiveJob provides a mechanism to take work out of the main request-response cycle between the browser and the application server. As a way to show how multiple different people could be served without receiving the wrong data, a unique UUID is generated in the ruby controller when the page is rendered and passed to the ActionCable channel and to the ActiveJob.
rails g job LoadGithubCommits
The job loads in the tags from Github’s API, parse them to JSON, then passes them to a partial for rendering. ActionCable broadcasts the request back to the client whose request_id
matches the ActionCable channel waiting for the commits.
class LoadGithubCommitsJob < ApplicationJob
queue_as :default
def perform(request_id)
tags = JSON.parse Http.get("https://api.github.com/repos/rails/rails/tags").to_s
ActionCable.server.broadcast "CommitTagsChannel:#{request_id}", {
tags: CommitsController.render( partial: 'commits', locals: {tags: tags}).squish
}
end
end
ActionCable Channel
The CommitTagsChannel kicks off the job when a client subscribes to the channel. It passes along the request_id
to keep track of the clients request.
rails g channel CommitTagsChannel
The channel, instead of the ActionController, is responsible for kicking off the job because a race condition occurs if the job is started by ActionController. The job may be finished before the ActionCable channel is setup, and the data would never be loaded by the client. Starting the background job via ActionCable guarantees the client is listening before anything is started off. There is nothing worse than speaking when no one is listening, and just as bad is processing data that cannot be sent over a WebSocket connection.
class CommitTagsChannel < ApplicationCable::Channel
def subscribed
stream_from "CommitTagsChannel:#{params[:request_id]}"
LoadGithubCommitsJob.perform_later params[:request_id]
end
def unsubscribed
stop_all_streams
end
end
Stimulus – Commits Controller
The Stimulus controller is responsible for setting up the ActionCable connection, and then inserting the HTML received over the wire into the page.
The Javascript for commit_tags_controller.js
, based off of a previous example, is:
import { Controller } from "stimulus"
import createChannel from "cables/cable";
export default class extends Controller {
static targets = ['tags']
connect() {
let thisController = this;
createChannel( { channel: 'CommitTagsChannel', request_id: this.data.get('request') }, {
received({ tags }) {
thisController.tagsTarget.innerHTML = tags;
}
});
}
}
A second controller comes over the wire. It takes a selected tag and pulls in data from a JSON API and templates it.
The HTML and Controller
This controller generates the request_id
as a UUID using SecureRandom. The HTML of the page starts with a single Stimulus controller, and a loading spinner.
<div data-controller="commit-tags" data-commit-tags-request="<%= @request_id %>">
<h1 class="title">Latest Rails Tags</h1>
<div data-target="commit-tags.tags">
<div class="columns">
<div class="column">
Refreshing Tags...
<svg width="44px" height="44px" viewBox="0 0 44 44" class="spinner show">
<circle fill="none" stroke-width="4" stroke-linecap="round" cx="22" cy="22" r="20" class="path">
</circle>
</svg>
</div>
</div>
</div>
</div>
The _commits.html.erb
partial takes the JSON response from Github. It loads each tag, and sets up the JSON templating Stimulus controller.
<div class="columns" data-controller="commits" data-commits-url="https://api.github.com/repos/rails/rails/commits?per_page=15&sha=">
<div class="column is-one-third">
<aside class="menu">
<ul class="menu-list">
<% tags.each do |tag| %>
<li>
<a data-action="commits#selectBranch" data-target="commits.option" data-tag-name="<%= tag['name'] %>"><%= tag['name'] %></a>
</li>
<% end %>
</ul>
</aside>
</div>
<div class="column content is-two-thirds">
<p>rails/rails@<span data-target="commits.branch"></span></p>
<ul data-target="commits.commits" class="content">
</ul>
</div>
</div>
Some More Practice
You can find all the code on Github at https://github.com/johnbeatty/actioncable_activejob_stimulus.
Here are two different practice scenarios to level you up:
I. Use a different API endpoint
Try this tutorial but with a different API endpoint.
II. Use a real life request identifier
Try this tutorial, but add in a different kind of mechanism for linking the background job with the client waiting for data.
Interactivity
This example shows the powerful tools provided by Rails that let you make interactive web apps, without a whole rewrite in the Javascript flavor of the month.
Feel free to leave a comment or question below.
Want To Learn More?
Try out some more of my Stimulus.js Tutorials.
2 comments on “Stimulus + ActionCable + ActiveJob: Loading Asynchronous API Data”
[…] An ActiveJob for each of the categories loads all the item ids in order in from the API, and then spawns new jobs to load in the individual items. The results are rendered into HTML, cached, and sent over ActionCable to anyone who is listening. Stimulus is responsible for setting up the ActionCable channels on the front end, and listening for changes to items and for changes to locations, like in this tutorial. […]
[…] An ActiveJob for each of the categories loads all the item ids in order in from the API, and then spawns new jobs to load in the individual items. The results are rendered into HTML, cached, and sent over ActionCable to anyone who is listening. Stimulus is responsible for setting up the ActionCable channels on the front end, and listening for changes to items and for changes to locations, like in this tutorial. […]