What generators are
Rails generators are command line tools that are used for automating the process of creating or editing files with boiler plate code.
In essence, they execute Ruby code much like a script and create or update files based on templates, user input and whatever logic necessary.
If you've played around with Rails, surely they are not a completely foreign concept to you. Many useful generators come bundled with Rails.
The model generator produces all files needed for a new model. It accepts a list of attributes and their respective types and generates files for the model, fixtures, migrations and unit tests.
Another example is the controller generator. It spins up a new controller with a specified list actions - adding their respective routes to config/routes.rb
.
Their purpose is to automate writing repetitive code that you would otherwise need to memorize or copy. Not only that, they help teams enforce standards for writing specific implementations as well as speeding up development.
For a list of all generators defined within a given application, invoke generate (alias g) with no arguments. This includes generators from gems and from your own code base.
$ bin/rails g
Let's dig a bit more into generators and then learn how to write a custom one for our own needs.
We will split generators into two types: repeated use and one-off generators. While these names are not official, they will help convey the conceptual applicability of each one of them.
Repeated use generators
Repeated use generators are meant to be used multiple times whenever a developer is doing a specific task. The model generator mentioned earlier is a great example. A developer will create many models while working on an application and the generator can be used to achieve it every time.
Let's examine an example on invoking the model generator.
$ bin/rails generate model User name age:integer group:references
invoke active_record
create db/migrate/20191002201613_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml
This is how this command can be broken down.
-
bin/rails
invokes the Rails executable CLI -
generate
is an argument for the Rails CLI telling it to invoke a generator -
model
is an argument for the generate command and represents the name of the generator -
User
is the first argument of the model generator, which is the name of the new model - Finally, all the other arguments are attributes with their respective types for the model generator:
my_attribute:my_type
. Omitting the attribute type defaults to a string
Repeated use generators are great for everyday tasks that simply require certain standards or are not worth memorizing.
One-off generators
One-off generators are procedures that need to run a single time to prepare a code base for some functionality. Typically, they are used to automate project configuration and gem installation, since these changes only need to be applied once.
Gems or frameworks that require some sort of configuration in place to work properly will often provide these as install generators.
A great example is the authentication framework Devise, which provides install generators to get an application ready to use its authentication features.
For a shorter example (and a shameless plug), let's take a look at how to use the install generator of a gem of my own: Sail. It is an engine for live configuration of applications and requires three things to work properly: a table for settings, a table for profiles and a config YAML file for declaring settings.
# user@~/workspace/my_awesome_rails_app
$ bin/rails g sail:install
create db/migrate/20191009202801_create_sail_settings.rb
create db/migrate/20191009202802_create_sail_profiles.rb
create config/sail.yml
After running the migrations, the app is ready to take advantage of all features provided by the engine. This type of generator is an outstanding way of providing a smooth experience for developers that want to use your gem.
Instead of writing long docs on how to install your gem, consider providing an install generator that will set up their project for them. We can even make the generator ask for input from the developer to properly tailor the result to their needs.
Creating a custom generator
For our convenience, one the generators bundled in Rails is the generator generator. That's right, a generator for creating other generators (after all, who would want to memorize all the boilerplate code for that?). Let's imagine we want to create a component generator.
bin/rails g generator Component
create lib/generators/component
create lib/generators/component/component_generator.rb
create lib/generators/component/USAGE
create lib/generators/component/templates
invoke test_unit
create test/lib/generators/component_generator_test.rb
Let's zoom in into each file and explore why they exist.
Usage
This file defines the usage instructions that will be displayed when invoking the generator with no arguments. This is how it looks.
Description:
Explain the generator
Example:
bin/rails generate component Thing
This will create:
what/will/it/create
We can change the copy to explain what the generator is supposed to do and what developers can expected from it. Invoking the generator with no arguments will then print the information we wrote.
For our example, we will create a component generator that accepts the name of the component.
$ bin/rails g component
Description:
Creates a component class
Example:
bin/rails generate component MyComponent
Creates the component class and unit test:
app/components/my_component.rb
test/components/my_component_test.rb
Component generator and its templates
The lib/generators/component/component_generator.rb
file defines the class that does all the magic. Here you can write whatever Ruby code is convenient to produce the output desired.
This class automatically runs every method defined in it, so you can think that each method you define is a step of the generator. Not only that, every method defined will also be available for the generator's templates.
Let's look at the generated Ruby file.
# lib/generators/component/component_generator.rb
class ComponentGenerator < Rails::Generators::NamedBase
source_root File.expand_path('templates', __dir__)
end
Currently, all the ComponentGenerator
does is define the source_root as the templates folder. Let's add a few things to it:
- The main step of evaluating the component template and creating the resulting file
- An example method that can be invoked from the template. In this case, it just returns a string
- A class collision check to prevent the accidental creation of files with a duplicate suffix (e.g.: MyComponentComponent)
# lib/generators/component/component_generator.rb
class ComponentGenerator < Rails::Generators::NamedBase
source_root File.expand_path("templates", __dir__)
check_class_collision suffix: "Component"
def create_component_file
# Template method
# First argument is the name of the template
# Second argument is where to create the resulting file. In this case, app/components/my_component.rb
template "component.rb", File.join("app/components", "#{file_name}.rb")
end
private
# Example method that can be invoked from the template
def my_method
"some string"
end
end
And now let's create a template inside the templates folder. Again, notice how the methods we add to our generator class are accessible to the template (including the ones that are defined by the parent class Rails::Generators::NamedBase
).
# lib/generators/component/templates/component.rb.tt
class <%= class_name %>Component
def initialize
@component_identifier = "<%= my_method %>"
end
end
At this point, our generator is working and can create components with the desired name. It also makes use of our method that returns a string.
Running
$ bin/rails g component Post
Results in
# app/component/post_component.rb
class PostComponent
def initialize
@component_identifier = "some string"
end
end
The last file created for us is a unit test for the generator itself. Rails provides a parent class for generator unit tests which contains a variety of helper methods for verifying the functionality we wrote.
Basically, we can assert which files are created and match against their contents.
class ComponentGeneratorTest < Rails::Generators::TestCase
def test_component_is_created
run_generator ["my_component"]
assert_file "app/components/my_component.rb" do |component|
assert_match(/class MyComponent/, component)
end
end
end
Next steps
There's a lot more that we can do with generators. Accepting arguments with specific types, asking for user input as the generator is running and test framework hooks to create unit test files for our component while respecting the testing library of choice (e.g.: minitest vs rspec).
I believed that including it all in a single article could be too dense. If the feedback is positive, I can turn this post into a series and explore more generator functionality. Please, let me know what you think in the comments!
Top comments (0)