At their core, Generators streamline the process of working with the file system and the contents of files in ways that dovetail nicely with Rails itself. And while much of the work of creating files and inserting text into existing files could be handled with Ruby’s FileUtils and related utilities, Rails Generators provide higher-level tooling to simplify things.

In short, custom Rails Generators are super-handy. The documentation, however, is split between the Rails Guides, Rails Docs, and Thor Docs—and they aren’t always documented extensively or consistently.

So I went spelunking through all of the available actions to write some plain-language guidance alongside examples for each. Where relevant, I’ve also made sure to add additional thoughts based on my experience using the various options—including the spots that tripped me up at first. And anywhere the documentation wasn’t 100% clear on which syntaxes were supported or how an action would behave under certain conditions, I reviewed the source code and created a test harness to run an example and see what it would do.

This post can serve as a quick reference, but more than anything, it’s organized to help tell the story of the particular tasks that Rails Generators simplify. If you’re looking for a more end-to-end guide, I’ve also written a higher-level post about creating custom Rails generators.

New Files/Directories

From Templates

template

If there’s one action that really represents Rails Generators, it’s Thor’s template action. It reads from the specified template file from the generator’s templates directory, processes the template contents using ERb, and saves the generated file to the specified destination.

While most of these actions could warrant a dedicated post, the template action is central enough to Generators that I’ve written an entire post about using Rails Generator templates. This summary can give you an idea of what the template action can do, but the blog post goes much further explaining how to use templates.

Given the number of moving parts with templates, there’s a good handful of things to remember so you don’t get tripped up.

  • By default, it expects the template files to be in the generator’s templates directory and end with a tt (Thor Template) extension.
  • The source argument is the template filename relative to source_root—which defaults to the generator’s templates directory.
  • The .tt extension for the source parameter is assumed and should not be included in the method call.
  • It’s not strictly necessary for a template file to end wth the .tt extension in order to be processed as ERb, but it definitely helps clarify the purpose of a file if the extension is there.
  • The destination argument should use the path and filename relative to destination_root—which defaults to Rails.root.
  • While Rails redefines the template method in order to extend it, the majority of the heavy lifting done by the template method is defined by Thor.

The most common usage calls the template method with a destination specified so the generated file can be placed in a specific location. In the next example, the generated file would end up at "#{destination_root}/app/models/sample.rb.

# Source Destinationtemplate 'sample.rb', 'app/models/sample.rb'

In the simplest case, the template method also works with only the source template specified, and the generated file will keep the same name. So in this example, the generator would generate the file to "#{destination_root}/sample.rb.

template 'sample.rb' # Uses same path/filename for destination

And finally, you can call the template method with a block, and the template’s content can be modified or expanded from within the generator logic. While this can be handy, I’ve found that it can muddy the waters if some of the content is stored in the template file and some is handled in the generator. So this wouldn’t be something I reach for regularly, but it’s handy to know it’s possible.

template 'sample.rb', 'app/models/sample_block.rb' do |content| "#{content}\n\n# This comment will be at the end of the file"end

js_template

Like the standard template action, it reads from a template file relative to the generator’s templates directory, processes any ERb in the file, and saves the generated file to the specified path relative to destination_root.

It’s not quite as flexible as the template method, and even the built-in Rails Generators don’t use it extensively. Ultimately, it’s calling template behind-the-scenes and removes the need to specify the js extension on the source and destination.

# Source Destinationjs_template 'sample', 'app/assets/javascripts/sample'

Behind the scenes, this is is equivalent to calling the template action and specifying the js extension.

template 'sample.js', 'app/assets/javascripts/sample.js'

I haven’t found myself reaching for this frequently because it’s not drastically more convenient. There’s certainly some potential for it handle more JS-related logic, but I haven’t found a need that justified exploring it further.

directory

Next to template, directory is one of the most helpful actions. You can almost think of it as a superset of the template command’s capabilities for an entire directory.

It recursively copies files from a given directory in the source_root, processes any ERb in template files, and puts the generated files into the specified directory relative to destination_root.

  • Everything that applies to the template action also applies to directory.
  • So I don’t get confused, I think of it as “create copies of all the contents of the source directory within the destination directory”. Otherwise, it’s not immediately clear whether the directory will be reproduced nested within the destination.
  • The file names can be auto-generated as well using %file_name%.rb-style naming. The generator will see file_name, call that method, and then use the returned value for the generated file name.
  • It is recursive by default, but adding recursive: false can override that behavior.
  • Files can be excluded from recursive copying by passing exclude_pattern: with a regular expression representing the file names to exclude.

The most common usage is similar to the template method and uses a source and destination folder. Like with templates, it will parse and process the ERb in any template files in the directory.

# Source Destinationdirectory "images", "app/assets/icons"

That call would result in copies of all files in images existing within "#{Rails.root}/app/assets/icons". That is, the generator is copying contents rather than creating the source directory nested within the destination directory.

One nice feature of the directory command is that file names can use the %method_name% syntax, and the generator will run the specified method and replace that portion of the file name. So if you have a file named %file_name%.rb in the source directory, the generator will copy it and rename it according to the generator’s value for file_name. So if you passed ‘email’ as the first argument to the generator, the generated copy of the file would be email.rb.

The destination path is technically optional, and leaving it off will create the copy in destination_root if not provided.

directory "app/assets/icons" # Destination is Implied

And finally, if you want to pass options to customize the behavior:

directory "images", "public/images", recursive: false

Creates a symlink to a file from the source_root. Uses a symlink by default, but you can pass symbolic: false for a hard link.

link_file "link_file.rb"link_file "link_file.rb", "link_file_renamed.rb"

This isn’t an action I’ve found extensive use for because the template-centric nature of Rails Generators makes it less practical. For example, using a symlink to a file in a Generator’s templates directory feels rather brittle because if the original template ever changes, the resulting link will change as well.

From Scratch

create_fileadd_file

Creates a file relative to the destination_root either by passing a string or a block to the action.

Like many of the Thor actions, you likely won’t need to reach for create_file too frequently in generators that use templates. Instead of explicitly creating files, you’d most likely let the template or directory action handle all of the file creation.

In some cases, however, you may need to create files in locations other than where the template or directory call would otherwise create them. Or your generator may need to sprinkle several different files in several different locations. So even if it’s not one you use regularly, it certainly comes in handy in the right contexts.

The create_file action works with either a string or block to create the content. For shorter content, passing a string works, but more often than not, I’ve found it handy to use Heredoc’s when using the create_file action.

create_file "email.rb", "class Email; end"

In that example, the content is short and sweet, but what if we wanted more content but not quite enough to justify creating a template?

create_file "#{file_name}.rb" do <<~CONTENT class <% class_name %> attr_accessor :key, :value end CONTENTend

Or what if we wanted to do something that was a little more conditional? In most cases, I’d lean towards templates if the content felt like it needed more than Heredoc, but sometimes there are benefits to having the content construction in the generator.

create_file "#{file_name}.rb" do content = [] content << "class #{class_name}" if attributes.any? names = attributes_names.map { |a| ":#{a}" }.join(", ") content << " attr_accessor #{names}" end content << "end" content.join("\n")end

copy_file

Think of the copy_file action as a lightweight template action that leaves off the ERb processing. You can use it in the same way as the template action.

# Copy the file and keep the namecopy_file "file.rb"# Copy and renamecopy_file "file.rb", "copied_file.rb"# Copy and modify the contents for the new filecopy_file "copy_file.rb", "copy_file_block.rb" do |content| "# Copied by `copy_file`\n\n#{content}"end

The template method will be more useful in most cases without much overhead, but if you want to create a copy of a non-template file, this is your simple solution.

Creates a file symlink to a file from the file system. Where link_file is more oriented towards leveraging the templates folder, the create_link action is more oriented towards creating a symlink using absolute paths to files elsewhere on the system. Sometimes, you just don’t need to create a copy of a file. You simply need a reference to a file stored elsewhere.

One quirk to remember with create_link is that the destination is the first parameter rather than second. Whereas many actions follow the “source-to-destination” order, creating a link is less like creating a file and more like inserting content where that content just happens to represent the path to the linked file.

# Destination: Source:# File to Create Absolute Pathcreate_link "new_etc_hosts.txt", "/etc/hosts"

Unlike link_file, create_link is independent of the templating system and merely creates a link that references a location on the file system.

By default, it will create a symbolic link, but you can override that by passing symbolc: false. (And if you aren’t familiar with the difference, a symbolic link points to a file by its filename whereas a hard link points to the same file even if the name changes.)

create_link "linked_file.txt", "linked_file.txt", symbolic: false

Like link_file, this isn’t an action I personally reach for regularly, but it’s another tool that’s worth knowing about since it can make it easier to connect system-level tools/resources to your application without using disk space by creating copies.

empty_directory

Creates an empty directory relative to the destination_root. To ensure it can be added to version control, it will likely need at last one file in the directory before the generator is complete.

empty_directory "empty-directory-name"

In situations where a generator is already using the directory command, you can add an empty directory within the directory to be copied, but you’ll want to ensure the directory has a .keep file. Otherwise, the generator won’t copy the directory, and version control may not recognize it as an empty directory.

Rails Files

initializer

Like the lib, rakefile, and vendor actions, the initializer action is syntactic sugar for generating a file in the config/initializers directory. It’s most likely to come in handy if you’re building generators for a Gem.

initializer("generators.rb", "# Initializer generators!")# ...is loosely equivalent to...create_file('config/initializers/generators.rb', "# Initializer generators!")

And like many of the other actions, it works by passing a block to generate the content.

initializer("generators-block.rb") do ['Load', 'Run', 'Generate'].map {|step| "# #{step}" }.join("\n\n")end

rakefile

Like the lib, initializer, and vendor actions, the rakefile action is syntactic sugar for generating a file in the lib/tasks directory.

rakefile 'generate.rake', 'puts "Generating!"'

That’s great if you need complete control over the rake task’s content, but if you want a placeholder rake file that already has placeholders for each action, you can use the built-in rake task generator since Rails provides a generate action that makes it easy to run a generator from within a generator.

You could use the rakefile action’s block syntax to automatically create a rakefile with a placeholder defined for each action…

rakefile("generate-block.rake") do <<~TASK namespace :generate do task :files do puts "Files generated!" end end TASKend

…or you could just use the task generator directly from within your custom generator…

generate :task, "task_name action_one action_two"

lib

Like the initializer, rakefile, and vendor actions, the lib action is syntactic sugar for generating a file in the lib directory.

lib("library.rb", "# Utilities can go here!")lib("library-block.rb") do <<~UTILITIES module Utilities end UTILITIESend

vendor

Like the initializer, rakefile, and lib actions, the vendor action is syntactic sugar for generating a file in the vendor directory.

vendor("vendor.rb", "# Vendor-specific stuff!")vendor("vendor-block.rb") do api_key = rand(10_000) "API_KEY = #{api_key}"end

Existing Files/Directories

Insert Content

With general content insertion, you’ll have to remember to explicitly include new lines (\n) and indentation in any content that you insert.

Rails has some low-level private methods (indentation, with_indentation, and optimize_indentation) that it uses for Gemfile indentation, and I’ve found that they can be used to help with custom generators.

The methods are private, however, so I’d exercise caution using them. Fortunately, the logic isn’t wildly complex, so the risk of using them is relatively low. Or you could use them as inspiration for your own indentation utilities.

prepend_to_fileprepend_file

Takes a string or a block and adds that content to the beginning of the specified file.

prepend_to_file "file.rb", "# Prepended via prepend_to_file with string\n\n"prepend_to_file "file.rb" do "# Prepended via prepend_to_file with block\n\n"end

append_to_fileappend_file

Takes a string or a block and adds that content to the end of the specified file.

append_to_file "file.rb", "# Appended via append_to_file with string\n\n"append_to_file "file.rb" do "# Appended via append_to_file with block\n\n"end

insert_into_fileinject_into_file

Prepending and appending content is nice, but sometimes, you just want to insert new content into a file either before or after a given string.

insert_into_file "file.rb", "# frozen_string_literal: true\n\n", before: "class InsertIntoFile\n"insert_into_file "file.rb", "attr_reader :first\n", after: "class InsertIntoFile\n"

inject_into_class

Like the insert_into_file action, but this inserts the content immediately after the class definition.

inject_into_class "file.rb", "InjectIntoClass", " ORDER = :alpha\n"inject_into_class "file.rb", "InjectIntoClass" do " FALLBACK_ORDER = :numeric\n"end

inject_into_module

Like the insert_into_file action, but this inserts the content immediately after the module definition.

inject_into_module "file.rb", "InjectIntoModule", " ORDER = :alpha\n"inject_into_module "file.rb", "InjectIntoModule" do " FALLBACK_ORDER = :numeric\n"end

Update Rails Files

add_source

Adds a gem source to the Gemfile by accepting just a string for the source or by accepting a string for the source with a block for specifying the gems associated with that source. With the former, the source will be added to beginning of the Gemfile, but with the latter, the source block is added to the end of the file.

# Inserts at the beginningadd_source "https://example.com/"# Inserts at the endadd_source "http://example.com/" do gem "example"end

gem

Adds a gem to the Gemfile with the included options.

gem "with-version", ">= 1.0.0", "< 2.0.0"gem "with-group", group: :testgem "with-lib-and-source", lib: "library", source: "http://gems.github.com/"gem "with-version-and-git", "1.0", git: "https://github.com/rails/rails"

One particularly handy feature of the gem action is that it supports including a comment before the gem. I’ve gotten in the habit of adding a short comment that describes every gem and, where relevant, adds some information about how it’s being used in the codebase so that it’s easy to quickly get a feel for the significance of a gem. So this feature can be really handy to create reminders for that.

gem "with-comment", comment: "Put this comment above the gem declaration"

gem_group

While the gem action makes it easy to add individual gems and even supports specifying a group, the gem_group action provides a convenient way to create a block for multiple environments and include gems for it.

gem_group :staging, :development, :test do gem "example-in-group"end

github

The github action provides a way to use Bundler’s ability to specify a repository from GitHub for a gem.

github "rails/rails" do gem "activerecord"end

route

Adds the specified string to the routes.rb file. If a namespace isn’t specified, it will be inserted just inside the routes block. If a namespace is provided, it will insert a block for the namespace if one doesn’t already exist, and then it will insert the route just inside the namespace’s block.

route "get 'help#index'"route "get 'dashboard#index'", namespace: :adminroute "get 'dashboard#show'", namespace: :admin

Assuming an empty routes file, these lines would create:

Rails.application.routes.draw do namespace :admin do get 'dashboard#show' get 'dashboard#index' end get 'help#index'end

environmentapplication

Adds a line the specified environment file. If an environment isn’t specified, it will add the line to application.rb. In those cases, the application alias helps make the intention more explicit.

environment " config.assets.compile = false\n", env: :development

The block syntax of the environment action can be a little awkward because it requires explicitly passing nil for what would normally be the content parameter.

environment(nil, env: :test) do "# This will go in config/test/rb"end

Thankfully, the application alias improves the clarity of adding files to application.rb since calling environment without a specific environment value feels a little clunky.

environment do "# This will go in application.rb"endapplication do "# This will also go in application.rb"end

Update Content

comment_lines

The comment_lines action lets you specify a file and a regular expression. It will then find all matching lines and prepend a comment hash with one space at the beginning of the line but otherwise leave any existing whitespace on the line untouched.

comment_lines "file.rb", /value = 0/

uncomment_lines

The uncomment_lines action works similarly to comment_lines but removes the comment hash and a single space for all matching lines. Any whitespace after the comment hash and the single space with it will be left untouched.

uncomment_lines "file.rb", /value = 0/

gsub_file

The gsub_file action is a handy shortcut for opening and reading a file, replacing some of the content, and then writing the results back to the original file.

gsub_file 'README.md', /# (README)/, '# My App Name \1'gsub_file 'README.md', /Deployment (instructions)/, 'Generator \1' do |match| match << " is fun!"end

Delete Files/Directories

remove_fileremove_dir

Delete files or directories. The remove_dir alias makes it a bit more intention-revealing if you’re deleting a directory rather than just a file.

remove_file 'file.rb'remove_dir 'tmp/throwaway'

Utilities

Find/Read Files

readme

The readme action might be one of the simplest of all. It reads and displays the contents of a file relative to the generator’s source_root. It will work both with and without the tt file extension on the file being read, but if there’s no ERb to parse, it’s always felt better to name it without the extension.

It’s a great example of Rails takes a simple tasks and makes them even more convenient through actions that provide convenient shorthand.

readme "README"

In particular, it makes a good fit for work-in-progress generators or even follow up guidance after a generator has been run. In the case of a work-in-progress generator, I might write up the equivalent manual process as plain text and then translate each of the manual steps into an automated step one at a time. Then as each step is converted from manual to automatic, it can be removed from the manual instructions.

With this methodology a process can be automated in small parts while still providing support for the manual process where the generator logic may be a little more complex.

Or, in some cases, generators simply don’t have the capabilities to do it all. In those cases, a “NEXT-STEPS” file in the template directory, could be printed to the screen. It wouldn’t need to be run through the generator, but it could easily be printed to the screen after running the generator.

get

The get action is defined by Thor, and it can make a web request and capture the resulting content of the web page to a file. The resulting file name can be specified as a string or generated within a block where the content can be used to generate a unique file name.

get 'https://example.com', "get.html"get 'https://example.com' do |content| "get_block_#{content.hash.abs}.html"end

And for more complex requests that might need specific HTTP headers in order to fulfill the request, you can use the https_headers option.

get 'https://example.com', "get.html", http_headers: {"Content-Type" => "application/json"}

find_in_source_paths

The find_in_source_paths action is mostly used by the other actions behind the scenes, but it’s a central part of the magic around generators and their template files. The key stems from the fact that when it looks for a given file, it looks for the file both with and without the tt extension.

That makes it easy to find any files in the templates directory regardless of whether they’re using the ERb templates functionality. It also means that virtually every action that takes a path value relative to the source_root uses it and will find the file whether the extension is present or not.

# This will find either...# - directory/overview.md# - directory/overview.md.ttfound_file = find_in_source_paths('directory/overview.md')

Due to the way the lookup works, if you have the same file name both with and without the tt extension, find_in_source_paths will return the first match. So if you ever see unexpected behavior, that’s one of those good things to double check.

Shell Commands

rails_command

With Rails, some logic and tasks happen via the command line rather than application logic, and Rails Generators make it easy to run those commands as well. So if you wanted to automatically run migrations after creating one, or clear the cache, clobber assets, run tests, or any other rake/Rails command, you can handle it directly from your generator.

rails_command :about

You can also specify the environment or pass any other supported options as keyword arguments at the end of the call.

rails_command "db:migrate", env: "test"

rake

Similar to the rails_command action, Rails also provides a rake action. I try to stick to the rails_command call, but rake is there if you need it.

rake :aboutrake "db:migrate", env: "test"

run

And when generators need to run a system-level command, there’s the run action. Pass it a string, and it will run the command in the context of the destination_root

run "ls"

run_ruby_script

In some cases, you may have a handy Ruby script to run. And while you could use the run action, the run_ruby_script makes a more considered effort to execute the script using the correct Ruby if you have multiple versions of Ruby installed.

run_ruby_script 'bin/sample'

generate

There are ways to extend the built-in generators, but in my experience, the most straightforward is simply calling a generator from within a generator using the generate command.

You pass the desired generator name and then pass the string of arguments, and Rails does the rest.

generate :model, "Sample name:string"

If you only need a generator to use different templates, you can customize them by overriding the generator templates, but if you want to modify more than a generator’s templates, using the generate action in a custom generator gives you maximum flexibility.

git

In some cases, you may want to run git commands on the changes, or you may even want to run a quick status check after running the generator. And Rails provides the git action for precisely that.

The most basic syntax simply calls a given command without any additional arguments.

git :status

But more complex commands with additional arguments can be called using the git command as a key and the command’s arguments as a string value for that key.

git add: `generated_file.rb`# Pass space-separated stringsgit add: "sample.rb.tt sample.js.tt"

And if you want to call multiple related commands, you can pass them as a series of keyword arguments.

git add: `generated_file.rb`, rm: "file_to_remove.rb"

chmod

Thor also provides the chmod action that makes it easier to ensure your generated files have the correct permissions. For example, if you have a generator that generates scripts, you can immediately make sure that the script file has execute permissions.

chmod 'bin/sample', 0755

Contexts

in_root

With all of the command line tools available, you may want to run shell commands within a specific context without bouncing around between directories.

For that, Thor provides the in_root method which accepts a block and then runs the block in the context of the destination_root.

in_root do run 'pwd'end

inside

As a corollary to the in_root action, the inside action let’s you specify a path relative to destination_root in order to run shell commands in the provided context.

inside 'bin' do run 'pwd'end

Conclusion

Between Thor and Rails, Generators have a plethora of convenient and powerful tools for generating files. While they aren’t exhaustively documented, rest assured they’re designed well to work seamlessly together and provide access to conveniences that range from saving a few keystrokes to actions that gracefully bundle several tasks down into a single method call.

Despite have long been curious about creating custom Rails Generators, I didn’t fully appreciate just how powerful they could be until I really dug in to understand everything they were doing and what they were truly capable of doing. Hopefully some of this knowledge helps clearly illustrate their potential for you as well.