Stimulus -ification

18 Apr 2022 . Rails . Comments #

At fountin we have an infra tool that uses rails to enable devs to deploy sha tags from Github to staging environment. It used some DOM manipulation to convert output of running kubernetes tools (helm).

I really wanted to hook up the automated testing suite that I helped build, but started first experimenting with changing dependencies.

Gemfile

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

Starting off with the ruby and rails versions so that we track main (see last 2-3 posts about joining pre-release and be as cool as Github also), we find the bump to 7 includes new javascript options.

Stimulus

chat it up with Hotwire or 👀 references]

This seemed to match our goal of swapping out elements with partials stored in our views folder, but with less boilerplate.

Coming from react it was nice to see that in the Controller all by fetch javascript can remain event oriented and the magic method generation soothed my rails-centric programming mental model I use.

import {Controller} from "@hotwired/stimulus";

export default class extends Controller {
    static targets = ['namespaces', 'selected', 'debug']

    connect() {
        this.fetchCloud();
    }

    fetchCloud() {

    }
}

We want the app to initialize by calling some function that will grab from the API (which currently resolves to a javascript respond_to block). To keep things simple, the controller gets respond_to { |res| res.html } and a view file ending in .html.erb with similar contents to the js:

 render partial: 'dashboard/namespace', locals: { namespace: @namespace } 

It isn’t a big change for this app, but the code that was stripped away was brittle jquery:

$("#create-namespace-log").replaceWith("<%= j(render partial: 'dashboard/namespace', locals: { namespace: @namespace } ) %>");

Now that the controller responds with the html, we can use javascript to fetch the partial loaded up with data asynchronously. But where is it? What actually receives the html partial?

<div data-controller="dashboard">
    <div data-dashboard-target="namespaces" ></div>
</div>

Finally we hook it up by

import {fetchNamespaces} from "../util/api";

fetchCloud() {
    fetchNamespaces()
        .then(response => response.text())
        .then(body => this.namespacesTarget.innerHTML = body)
    
    }
}

Finally we add actions with a button in the partial. These buttons lined up like tabs on a console.

<% metadata = namespace['metadata'] %>
  <td>
    <button data-action="click->dashboard#select" data-namespace="<%=metadata['name'] %>">
      <%= metadata['name'] %>
    </button>
  </td>

And extend our fetchCloud()

    select(event) {
        event.preventDefault();
        fetchReleases(event.target.getAttribute('data-namespace'))
            .then(response => response.text())
            .then(body => this.selectedTarget.innerHTML = body)
    }

This time we are customizing the API call by passing in the namespace we stored on the rendered partial.

Being a very simple application, stimulus orchestrates a pattern that is easy to maintain.