This post focuses entirely on breaking down how templates work with Rails Generators. If you’re looking for a higher-level post that provides a more end-to-end guide, you may want to check out “Creating Custom Rails Generators” that zooms out to set up a broader understand without going as deep into specific topics.

The individual features of Rails Generators aren’t particularly fascinating by themselves, but the ways that Rails integrates all of the moving parts into a convenient package—including testing-makes it all much more interesting.

Somewhat ironically, though, in order to appreciate and be productive with your own custom Rails Generators, understanding the pieces in isolation can help demystify the big picture. So we’ll focus in on the templates functionality today. The core of the templates functionality originates from Thor, but Rails adds some magic and syntactic sugar to ensure they work smoothly in the context of Rails.

While templates are one of the more advanced parts of Rails Generators, conceptually, they’re relatively straightforward. (Figure 1) We have a source directory with some template files written in ERb, the generator itself, and a destination directory for the generated files.

[A diagram with the source directory (via the source_root value) on the left with an arrow to the right connecting it to the generator and then the destination directory (via the destination_root value) after that.] A diagram with the source directory (via the source_root value) on the left with an arrow to the right connecting it to the generator and then the destination directory (via the destination_root value) after that.
Figure 1

Conceptually, templates are fairly straightforward. A generator checks its source directory using the source_root variable and then places the generated files into the destination directory based on the destination_root variable.

↩︎

The template action can then be called from the generator where it combines the file system work and ERb parsing to create a more streamlined way to generate files. (Figure 2) It relies on a single method with two parameters: template "file.rb", "app/models/file.rb", but that’s somewhat deceptive as the Rails pre-configures the source and destination root values for Generators.

With that configuration, Generators have the knowledge to fill in the blanks and turn the ERb template into the desired destination file, but there’s some Rails (and Thor) magic that enables this. So we’ll break it down to understand how it’s all working so we can more efficiently work with Generators.

[A diagram showing the `template` method with its `source` and `destination` parameters.] A diagram showing the `template` method with its `source` and `destination` parameters.
Figure 2

The template method is pleasantly simple. This diagram maps the concepts from (Figure 1)  to the method that makes it work.

↩︎

Pieces of the Puzzle

With templates, we have a handful of parts that make it all work seamlessly. We’ll briefly run through the pieces involved, and then we’ll dive into the details for each of them while covering some tips and tricks to ensure smooth operations.

First, the source (source_root) and destination (destination_root) directories provide predictable locations for the generator’s file system work. (Figure 3)

[A diagram illustrating that the default destination_root value points to Rails.root, the default source_root value defined by the generator points to the generator’s templates folder, and then destination_root value in test cases points to the tmp/generators directory.] A diagram illustrating that the default destination_root value points to Rails.root, the default source_root value defined by the generator points to the generator's templates folder, and then destination_root value in test cases points to the tmp/generators directory.
Figure 3

By default, new Rails Generators will have the source_root set to the generator’s templates directory, and the destination_root defaults to Rails.root. In testing, the destination_root is overridden to put files generated by tests into tmp/generators.

↩︎

Next, the templates themselves provide the ERb support from within the generator’s “templates” folder in each generator’s source directory where Rails can recognize them as templates to be read and parsed into the resulting generated file.

Then the template action serves as the glue within the generator by specifying the source template file name paired with the desired path for the generated file.

And finally, Rails provides some Generator-specific testing utilities help streamline verification without requiring a bunch of low-level file system cleanup before and after the tests.

Now that we’ve got context, let’s look at each of those pieces individually.

Source & Destination Directories

The templating functionality in Rails Generators relies on knowledge of the source and destination directories, and Rails creates an empty templates directory as the default source_root value each time it generates a new Generator. And if we look in a generator’s initial Class file, we see precisely one line of code setting the source_root by pointing it at the templates directory.

# Set source_root to the 'templates' folder in the current file's directorysource_root File.expand_path("templates", __dir__)
Figure 4

By default, newly-generated generators create a ‘templates’ folder and set it as the generator’s source_root.

↩︎

With that line, the dots are connected so we shouldn’t have to give a second thought about where the generator will look for the template files. You could set it to a different directory, but you shouldn’t need to for the vast majority of use cases. (Figure 5) But there’s still another dot that needs to be connected.

The destination_root is the corollary to source_root. By default, the destination root is set to Rails.root, and in most cases, that’s what we want. The customizable destination_root primarily comes in handy for automated testing in order to redirect the generated files to /tmp—or somewhere equally disposable. That way we don’t have to worry about junk files accidentally being generated within the application or committed to the repository.

[A modified version of the `template` method diagram with the source parameter set to ‘file.rb’ (the template file) and the destination parameter set to ‘app/models/file.rb’ (the generated file).] A modified version of the `template` method diagram with the source parameter set to 'file.rb' (the template file) and the destination parameter set to 'app/models/file.rb' (the generated file).
Figure 5

Expanding on the template method a bit, it ensures the source and destination parameters are as simple as they can be for the template file and the resulting generated file since it the source_root and destination_root values don’t need to be specified every time.

↩︎

It may feel minor, but these source and destination settings play a key role in saving us from having to type something complex and unreadable to something much easier to type and read. (Figures 6 & 7)

template "lib/generators/#{generator_name}/templates/file.rb.tt", "#{Rails.root}/app/models/file.rb"
Figure 6

If Rails Generators weren’t using source_root and destination_root values, the call to the template method would be much more verbose and much less readable.

↩︎
template "file.rb", "app/models/file.rb"
Figure 7

Thanks to Rails Generators (Thor, really) leveraging source_root and destination_root, the template call is much easier to type and more readable.

↩︎

On the surface, all of this may look like it only saves some typing, but it also makes that template call much more readable. Without the directory and file extension obfuscating the template file name, we can more quickly recognize which file we’re referring to.

If you’re reading closely, you may have also noticed the .tt extension in the first example is missing from the second. The template method assumes the file extension so we don’t need to specify it either. (If you’re curious about that extension, hold that thought. We’ll get to it in the next section.) (Figure 8)

[An updated diagram of the `template` method where the source and destination are replaced with examples that show the implicit values for the parameters.] An updated diagram of the `template` method where the source and destination are replaced with examples that show the implicit values for the parameters.
Figure 8

When we break down what’s happening with the template method call, the source file name has the source_root (lib/generators/#{generator_name}/templates) pre-pended and the .tt extension appended for looking up the source file. The destination has the destination_root (Rails.root) value pre-pended.

↩︎

If you’re underwhelmed so far, I get it. This all looks relatively simple once you see how it works, but that’s the beauty of generators. They are that simple. The magic stems from how all of the conveniences and conventions add up and work together to make it all seamless.

Getting the Destination Right

For simplicity, I’ve used basic string concatenation for the destination path up to this point, but there’s one more thing worth keeping in mind. Let’s look at an example template call for context. The source parameter is simple enough because it only needs the template file name, but the destination will be slightly more involved if we want to get it just right.

template "task.rb", File.join("lib", "tasks", "#{file_name}.rake")
Figure 9

While the path to the source file is handled automatically, we want to use File.join to specify the pathname for the destination in order to maintain maximum compatibility across operating systems.

↩︎

In this example, the destination path uses a File.join call instead of pure string concatenation. This is one of those small things that ensures the code works on Windows, Unix, or Mac because File.join uses the appropriate directory delimiter for the current system. (i.e. \ on Windows and / on Unix/Macs.)

Learn to Build Custom Generators that Save Time Learn to Build Custom Generators that Save Time

Skip the copy/paste and search/replace dance. Learn how custom Rails generators can help save time for you and your team. I’ve put together a free email course to help demystify things so you can create custom generators quickly and efficiently.

No-nonsense, one-click unsubscribes.

Template Files

Now that we’ve established some context on where templates live within generators, we can talk about the template files themselves, file names, ERb, and that tt file extension.

File Type Extension

Any files in the templates folder need to have the .tt (short for “Thor Template”) extension after the primary extension. But like we saw in the earlier example, the .tt extension isn’t necessary in template calls. Like the source_root, the extension is handled automatically.

For example, with a file named model.rb, Rails and Thor expect the template file to be named model.rb.tt. The extension helps identify the file as a template, but more importantly, it ensures Rails never tries to autoload template files.

Of course, the magic of templates stems from supporting dynamic content through ERb. That content could be plain text, but the power stems from using ERb to make them dynamic and leverage Rails helper methods. For simplicity’s sake, you can think of any template file with the tt extension as being ERb.

And since templates are processed in the context of the generator, any methods available within the generator are also available within the template files as well. That brings us to some of the string-oriented conveniences provided through Rails’ inflections.

Rails Inflections

With generators and templates, Rails’ inflections and supporting methods for the name argument come in really handy. Any generators that inherit from Rails::Generators::NamedBase also inherit a name argument as the first command line argument by default. Rails then uses that value for a set of common string variants and inflections that streamline the ERb and file names.

rails g my_generator Animal::Pet::Dog
Figure 10

When we have a name attribute on a generator (the default when inheriting from Rails::Generators::NamedBase), the first argument gets assigned to name and then provides convenient access to a variety of inflections from rails.

↩︎

With that generator command, we end up with "Animal::Pet::Dog" as the name attribute for the generator. (Figure 10) Bear with me on the gratuitous use of namespaces because they help illustrate the robustness of the various inflections. Now we have access to a handful of methods that convert that name into a variety of useful formats to use in the generator and templates…

  • table_name - “animal_pet_dogs”
  • singular_table_name - “animal_pet_dog”
  • human_name - “dog”
  • fixture_file_name - “dogs”
  • i18n_scope - “animal.pet.dog”
  • file_name - “dog”
  • file_path - “animal/pet/dog”

…and too many more to cover here, but the Rails::Generators::NamedBase documentation covers them all. You may even recognize the patterns of those resulting strings because that’s how Rails does its thing when the built-in generators to create models, migrations, fixtures, tests, and controllers.

When necessary, we can also include the Rails::Generators::ResourceHelpers module for a handful of additional controller-related convenience methods.

ERb Delimiter Escaping

For most file types, using ERb is straightforward, but things get a little meta when we need an ERb template to generate actual ERb. In that context, we need to escape the ERb delimiters (i.e. <% and %>) that we don’t want the generator to process.

To do that, we add an extra percent sign to the delimiter. So instead of <% to open an ERb block, we use <%%. The generator will process any unescaped ERb, and while processing it will convert the escaped delimiters to standard delimiters in the resulting file so we end up with working ERb.

Testing

Testing is the last part of the equation, and Rails provides some conventions and a handful of additional assertions for generators that simplify the process of verifying that the expected files exist and that their contents were generated correctly.

Discarding Generated Files

When we use the Generator generator, Rails automatically creates the test file and includes a line in the file that redirects the destination_root to tmp/generators so that the files generated during tests don’t end up in our repository. (Figure 11) Rails also ensures that the destination files are cleaned up around each test so we never have to think about manually cleaning up leftover files after running tests.

This is another one of those little details that really helps make the process of creating custom generators run much more smoothly. And while you shouldn’t need to write that line yourself since it’s automatically generated, it’s worth noting that the method used is destination rather than destination_root. This is notable because source_root is used to specify the source root value within the generator.

destination Rails.root.join("tmp/generators")
Figure 11

Since generators create files, testing manually can be less-than-efficient. Fortunately, Rails updates the destination_root to automatically route the files generated by tests to the tmp directory so the generated files don’t linger and clutter up our repository.

↩︎

Assertions for Generators

With the test destinations sorted, let’s talk about the assertions Rails provides for testing generators. In particular, assert_file (or its corollary assert_no_file) will be one of the more common assertions, and it works several ways depending on the complexity of the generated file.

The versatility of assert_file supports several different syntaxes. The first and simplest approach uses it to verify that a file exists in the destination without regard for the content. The second version lets us add a regular expression to verify some content exists in the file. And the third approach accepts a block to perform more-granular verification of the content. (Figure 12)

# Simplest assertion for a file's existenceassert_file "app/models/file.rb"# Simple assertion to verify the file's existence and the presence of specific contentassert_file "app/models/file.rb", /initialize/# Assertion to verify the file's existence and use a block to perform more granular content verificationassert_file "app/models/file.rb" do |content| assert_match(/initialize/, content) assert_match(/call/, content)end
Figure 12

Since generators create files, testing manually can be less-than-efficient. Fortunately, Rails updates the destination_root to automatically route the files generated by tests to the tmp directory so the generated files don’t linger and clutter up your repository

↩︎

We can get pretty far testing generators with just those approaches, but there are some more specific assertions that we can use to verify the generated contents of a file.

  • assert_match isn’t specific to generators, but it lets us use regular expressions or strings to verify that a specific string appears in the content of the block.
  • assert_method/assert_instance_method lets us verify that an expected method is correctly defined
  • assert_class_method lets us verify that a class method is correctly defined, but it will not recognize class methods in a class << self block.

When using one of the method assertions, we can pass a block that makes assertions about the contents of the method if it’s found. (Figure 13)

assert_file "app/models/file.rb" do |file_content| assert_method :initialize, file_content do |method_content| assert_match "@name = name", method_content endend
Figure 13

With many of the generator assertions, we can use nested blocks to verify content within the file or within methods.

↩︎

Relative to some of the other magic we get from Rails, generators feel less magical and just make sense. Rails provides some utilities that help with testing, and with some reasonable conventions around the source and destination paths, Rails implicitly knows a lot about your generators. This then reduces the amount of boilerplate code needed to test them quickly and completely.

This is only skimming the surface of testing generators, but we’ll cover testing more robustly in a separate post dedicated entirely to testing generators.

Conclusion

Templates represent one of the core features of Rails Generators, and while they’re relatively straightforward compared to other development tools, they wrap a fair amount of complexity under the hood so you don’t have to think about it.

Naturally, templates are just one of the tools that make generators so helpful. We’ll dig into the various actions provided by Rails and Thor, handling command-line arguments and options, testing, and more. I’ve also written a broader guide on custom rails generators if you’re looking for something more pragmatic that covers the full picture.