The deeper I’ve gone into exploring and experimenting with Rails Generators and Thor, the more it feels like creating custom Rails Generators is the underutilized productivity tool both within projects and across projects.
Rails Generators are not wildly complex tools. Most developers could likely cobble together something vaguely similar in a weekend. The magic stems from the fact that they’ve already cobbled it all together in a cohesive way so they can be fast to build reliably and use over and over again.
Rails Generators are just one of those things where the whole is greater than the sum of its parts. It’s another case of a little convention going a long way.
Before we start having fun, I want to acknowledge that much of this functionality stems from Thor rather than directly from Rails Generators, but for simplicity’s sake, we’ll look at all of this through the lens of Rails because that’s the context from which most folks will be exposed to this functionality.
Being able to run a text file through an ERb parser isn’t particularly magic. But everything changes when that power is available through a single method call that accepts two simple arguments. It goes from being a task with a lot of configuration to a frictionless one-liner that easily becomes second nature.
Moreover, with the
directory command, we can recursively parse multiple ERb files into generated files and dynamically rename those files in the process with a single call to
directory('some/location). I can’t think of another case where one single line of code saves me from so much tedious work.
Without automated testing, generating and manipulating files can be wildly tedious and error-prone. Even generating two files and manipulating a third would require manually locating the files, opening them, looking at them, and then carefully undoing the changes to try again. Of course, we could automate that step manually, but then we’re sending even more time.
If templates are the core of generators, it’s the automating testing integration that really makes them special. Rails Generators are one of the few areas of Rails that get their own dedicated test cases and assertions. And the automated tests clean up after themselves automatically. So we don’t accidentally generate a file somewhere on the system and forget that it’s there.
First-class Argument & Option Support
Any CLI tool needs a way to receive information from the command line and translate it to usable data in a script. There are endless ways to do this, and if you’ve ever tried to build a basic command-line script from scratch, you likely know that choices around handling arguments and options can be confusing and overwhelming.
With Rails Generators, it’s just there. It’s integrated. And it works. Once you know what’s available and how to define the arguments and options, your generator can seamlessly slurp up arguments and options into formats that you can access directly in your code.
Conventional Options Pre-configured
While we can easily add our own arguments and options to generators, we get a few conventional options for free automatically. Without any effort on our part, Rails Generators automatically get a handful of conventional options for free.
Our end users can specify
-q to suppress output,
-p to do a test run to see what the generator will create,
--force to automatically overwrite duplicate files, and
-s to leave existing files untouched.
Just Enough Error Handling
Not only do we get integrated arguments and options, but when someone goes to use our generator and makes a mistake with the arguments or options, our generator will let them know without us having to write any error handling. It’s not the fanciest or most advanced error handling, but it’s enough to help ensure our generators aren’t confusing to use.
When the string values are translated into numerics, booleans, arrays, or hashes we get some basic type checking. When fields are required, it lets users know when they’re missing. All of this happens without writing a single line of validation logic. You can, but you generally don’t need to.
So many tools require adding yet another dependency to your codebase. Generators are already there. There’s no extra configuration or dependencies. Rails even gives us a built-in Generator Generator so it’s fast and easy to start building a new generator.
Beyond the fact that generators are implicitly available to every Rails app right away, but they come with a plethora of utilities and tools to make it even easier for the generators to handle a wide variety of tedious file-system tasks with a single line of code.
With any project, anybody can write a script from scratch. But then we’d have to ensure that script is discoverable and add documentation about how to use it. While it’s technically possible to build a Rails app without generators, I’m willing to wager that we’re pretty unlikely to meet a Rails developer who hasn’t used generators extensively.
They may not be intimately knowledgeable about how to build them, but it’s a safe bet that anyone on a team building a Rails app knows that generators exist and how to use them. And once we build a generator, it will automatically show up in the list when someone runs
rails generate. Discoverability is handled. We can be confident they’ll know how to use them. And we can be confident the documentation will be there.
Speaking of documentation, Rails Generators are inherently self-documenting. Even just the defaults can often be enough to explain the usage of a custom generator, but we also have extensive options for adding clarification to our arguments and options with
desc keyword options.
And if we define aliases or defaults, our end users will automatically see those in the documentation as well. And anywhere the automated documentation isn’t enough, the free-form
USAGE file gives us a place to fill in the gaps, and the generator command will display the details alongside of the automatically-generated portions.
It feels almost silly to point out Rails integration as its own thing, but having our generators essentially serving as an extension to Rails means we also get bucket loads of conveniences. Generators have a full suite of inflections available to them so we can pluralize, singularize, and generate file and directory names with no effort at all.
Mix in introspection capabilities for any existing classes and models, and we’re giving some pretty convenient tools for generating well-integrated code. And that integration doesn’t just stop with building generators—they’re seamlessly integrated with our automated testing as well. And Rails Generators also give us the tools to modify our Gemfile, create a new library or code snippet in
/vendor, or create a new initializer in
config all with simple and concise methods.
With Rails Generators, it’s often the little things, and boolean options are one of those very Ruby-like little things. If we define a boolean option named
thing, we can specify a true value simply by including
--thing in our command line call. But that’s not where it ends. We can also explicitly specify a false value with
--no-thing. That sure beats
And because our options are exposed as a hash, we can use
options.thing? from within our generator to check for the truthiness of whatever value is or isn’t in that option. While this serves as a presence check for non-boolean values, it makes it super-easy and readable to write simple code to branch on a boolean option.
Streamlining File System Work
I’m sure we all love creating new files, inserting a configuration line into another file, and then doing the copy/paste and search/replace dance. Or maybe some people prefer to crank out a bunch of
FileUtils code. But on the off chance you’d rather spend time on more interesting work, Rails Generators provide us with tools that wrap multiple lower-level patterns into concise and memorable actions.
Moreover, because Rails Generators are smart about source and destination paths, we don’t have to spend time thinking about where a file will go or look up what the relative root is for a path. The conventions enable us to confidently type one line and leave all the lower-level file system work to the generators. Remember those pre-defined options to skip or force overwriting files? It just works.
Generators can also do more than just create and manipulate text files. They can call commands, run scripts, and even call other generators. While this part can be a little overwhelming at first, Rails Generators provide several options to call other generators. We can call them directly with
generate or we can add hooks to call related generators automatically.
Once Rails Generators become second nature, the sky’s the limit. We can move from thinking of each generator as a distance thing and start thinking of them more as composable building blocks that are well-tested, reliable, and progressively enable us to build more advanced generators and save more time.
Whether we build a generator as part of a process to streamline a gem or we release a generator as its own gem or just share it as a gist, they are highly portable. If we’re careful enough, we can write generators that will work in any recent Rails project. Or if we follow a set of internal standards, we could share the gems across multiple projects within a company so that multiple team members can benefit from them.
And of course, within a project, we can write a very specific and helpful generator that all of our teammates can leverage and extend. Then it’s not just about how much time generators can save us individually but how much time and effort they can save us collectively.
When our project matures and we’ve got a consistent and predictable approach for new webhook receivers, query objects, adapters, partials, observers, decorators, presenters, helpers, PORO’s, POAMO’s, or whatever patterns we’re using, we have a choice. We can write a document that explains how we do it and why and have our teammates manually trudge through tedious and error-prone work. Then we can then wait for that document to become stale and outdated and waste time hashing it out in a pull request.
Or we can take that same time to build a new generator in half-an-hour with reliable test coverage and be confident that it won’t break or become outdated because it’s tested every time we run our test suite. Moreover, our teammates go from reading prose and translating it to file system work to typing a single command that generates all the files and placeholders they need in seconds.
Let’s Save Time
We all use and appreciate the built-in generators. We know they save us time. They may not feel perfect, or they may feel like a black box. But they don’t have to. They initially have a somewhat steep learning curve relative to the other parts of Rails, but they also have the ability to rescue us from tedious development work.
I wasted years dancing around building custom generators because I always got off to a slow start, and they didn’t feel like they’d be a net time saver. Then I sat down and worked through a few, and now they’re second nature. That means I can create a solid new generator in minutes instead of hours or days. The hardest part now is having the discipline not to reach for them too much.