Building a custom Stimulus generator for Rails

If you’ve spent any time working with Ruby on Rails, you know that using generators can save you from a lot of console commands and copy/pasting from one file to the next. Need a new model? Just run rails g model MyNewModel name:string description:string and you’re set.

Rails covers most of the common use cases for you with built-in generators. If the built-in generators don’t meet your needs, you can get fancy by creating your own custom generators. Custom generators can save you time any time you’re finding yourself running lots of touch commands and copy/pasting boilerplate into new files.

Although Rails provides built-in generators for most things that Rails projects need, one common use case that’s missing is a generator for new Stimulus controllers. Stimulus , a modest JavaScript framework created by the team at Basecamp, is an increasingly common part of the Rails stack. Despite its growing popularity, Rails does not provide a generator out of the box (as of Rails 6.0). Without a generator, every new Stimulus controller requires a manual touch app/javascript/controllers/my_new_controller.js in the console and then copying from another controller, changing the class name, and otherwise doing stuff uninteresting stuff before you start building something new. No fun.

Today we’re going to solve this problem by learning how to build our own custom generator for Rails. Our custom generator will create new a new Stimulus controller in the app/javascript/controllers directory when we run rails g stimulus ControllerName.

Let’s get started.

Setup

I’m writing this assuming that you have a Rails 6 project and you’re working from that project in your terminal.

If you don’t have a Rails project handy, just run rails new stimulus_generator_project and then cd stimulus_generator_project to follow along.

Generating a generator

Rails is known for valuing developer experience, and Rails generators are a prime example of that love for developers. Rails provides a ton of generators out-of-the-box including a generator to generate new generators. Very meta stuff, and very convenient for us.

We’ll use the generator generator to generate a new Stimulus generator. Still with me? Great. Run this in your console and let’s move on from generating generators:

rails g generator stimulus
  

This applies Rails magic to create the bones of our generator, named stimulus, in the lib directory. You’ll notice that inside of the lib/generators/stimulus directory there are a few files and an empty templates directory. We’ll briefly talk about what each of these items is and then we’ll create our generator.

A screenshot showing the console output of the rails g generate stimulus command run in the previous section.

stimulus_generator.rb: This is the file that runs when you type rails g stimulus in your console. The class defined here will manage creating a new file each time the generator runs, and populating that file with the content from our template Stimulus controller.

USAGE: This file contains instructions for users of your generator. The contents of this file are printed when you type rails g stimulus in your console.

Templates: The templates directory contains any files that your generator will copy when it runs. In our case, we’ll just have one file. These template files can access and insert variables into the template, when needed.

stimulus_generator_test.rb: The generator generator helpfully creates a test file, which can be run (in our case) with rake test. Our generator is simple, but we’ll add a test to make sure it works as we expect.

Adding our code

As a reminder, we’re building a generator to create a new Stimulus controller. This generator will only accept a name as an argument and will always create the controller in the app/javascript/controllers directory when it runs.

Let’s first add a test to our stimulus_generator_test file. We’ll use this test to make sure our generator works without having to generator a Stimulus controller we don’t want.

In lib/generators/stimulus/stimulus_generator_test.rb add a test that runs our generator and validates that a new file is generated as expected:

require 'test_helper'
require 'generators/stimulus/stimulus_generator'

class StimulusGeneratorTest < Rails::Generators::TestCase
  tests StimulusGenerator
  destination Rails.root.join('tmp/generators')
  setup :prepare_destination

  test 'It generates a controller in the app/javascript/controllers directory' do
    run_generator ["Hello"]

    assert_file "app/javascript/controllers/hello_controller.js"
  end
end
  

When we run rake test the test will fail, which is expected since our generator hasn’t been built yet. Let’s work on making it pass.

First, create the template file that our generator will copy each time it runs. This file will live in the templates directory:

touch lib/generators/stimulus/templates/controller.js
  

In this new file, we’ll insert the content of our Stimulus controller:

import { Controller } from 'stimulus'

export default class extends Controller {
  static targets = []

  initialize() {
    // Called once, when the controller is first instantiated
  }

  connect() {
    // Called any time the controller is connected to the DOM
  }

  disconnect() {
    // Called any time the controller is disconnected from the DOM
  }
}
  

My template includes the basic structure of a Stimulus controller plus the lifecycle methods I find myself using most often, that’s what makes the most sense to me. You can include more lifecycle methods or just leave out everything but the class definition, you’re the boss of your own template file.

With our template file created, we can go into our generator file and tie the generator and template together:

class StimulusGenerator < Rails::Generators::NamedBase
  source_root File.expand_path('templates', __dir__)

  def create_controller
    template('controller.js', File.join("app/javascript/controllers/#{file_name}_controller.js"))
  end
end
  

Each time rails g stimulus Something is called, any method in this file will run. In our case, we just create a new file from our template using the template method (provided by Thor, if you’re interested in the inner workings of Rails generators). If we want to handle conflicting file names, make our file name generation smarter, process additional arguments or otherwise make our generator more powerful we can add additional methods to do so. For our purposes, we will keep things simple.

With our template and our generator class built, let’s see if our tests pass:

A screenshot of the result of running rake test in the console with all tests passing.

Perfect! We’ve got a working generator and we can run that generator with rails g stimulus CoolIdea.

If you’ve followed along with me through this tutorial, when we run the generator our new controller will be created at app/javascript/controllers/cool_idea_controller.js and it will be populated with the content from our controller template file.

Congratulations on making it this far! You’re a star.

Our last step is to update the USAGE file for our generator with useful information for others who encounter our generator in the future. Something like this will work fine:

Description:
Stubs out a new Stimulus controller.
This generator accepts one argument, the name of the controller, CamelCased or under_scored.
Example:
`rails generate stimulus Thing`
This will create a new Stimulus controller at `app/javascript/controllers/thing_controller.js`
  

Now when we run rails g stimulus our help text will be printed to the terminal. The generator is pretty simple today, but in the future we might add additional arguments or options. Starting with documentation from day one is always the better plan.

Wrapping up

Today we learned how we can use the magic of Rails to create our own generators and we used that knowledge to fill a gap in the Rails defaults by creating a simple generator to add new Stimulus controllers.

While our example was simple, the same technique can be applied to create sophisticated new generators for Service Objects, complex Stimulus controllers, or any other resource you find yourself needing. In addition to creating our own custom generators, we can also use this knowledge to change the default generators provided by Rails. Overriding the default model or controller generators can help your organization enforce code standards across an organization or just save you from copying boilerplate across your project.

For more reading on the power of Rails generators, start with the Rails guide on generators. Finally, if creating your own Stimulus generator isn’t a chore you’re looking forward to, I’ve published a gem you can use to add Stimulus generator just like the one in this tutorial to your Rails projects.

Thanks for reading!