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 att
(Thor Template) extension. - The
source
argument is the template filename relative tosource_root
—which defaults to the generator’stemplates
directory. - The
.tt
extension for thesource
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 todestination_root
—which defaults toRails.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 destintaion_root
.
- Everything that applies to the
template
action also applies todirectory
. - 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 seefile_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
link_file
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_file
add_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.
create_link
add_link
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_file
prepend_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_file
append_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_file
inject_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
environment
application
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_file
remove_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.