What Are Custom Generators in Rails?
Rails generators are command-line tools that automate the creation of files in your application (models, controllers, tests, etc.).
โ
Custom Generators allow you to create your own rails generate
commands to scaffold files, boilerplates, or templates specific to your app’s architecture or team conventions.
โ Why Use Custom Generators?
Problem | Solution with Custom Generator |
---|---|
You repeat similar files in every module | Automate it with a generator |
Your app uses service objects, decorators, or presenters | Add generators for each pattern |
Your team follows a custom folder structure | Enforce it using generators |
Want to standardize code generation in your team | Use one command for consistency |
Custom generators = DRY code + faster development + consistent structure
๐ Important Concepts for Beginners โ Custom Rails Generators
๐งฉ Concept | ๐ฌ Description |
---|---|
Rails Generator | A command-line tool in Rails that helps scaffold code (rails generate model , controller , etc.). You can build your own custom version for repeated patterns. |
lib/generators/ | The directory where custom generators live. Rails looks here to find your generator class and templates. |
NamedBase | A helper base class (Rails::Generators::NamedBase ) that provides access to file_name , class_name , etc., from your generator name. |
.tt file | A template file used by generators. It stands for “template text” and contains ERB (<%= %> ) for injecting Ruby code into file generation. |
template method | A method in generators that takes .tt and writes a final file after replacing dynamic values. |
source_root | Tells the generator where to find .tt files. You usually set this to the templates folder. |
argument | Defines input passed to the generator (like user_params or method names). These are available as instance variables. |
class_name | A helper from NamedBase that converts your input (UserSignup ) to class format (UserSignup ). |
file_name | Another helper that converts input to snake_case (user_signup ). |
<%= %> in .tt | Outputs Ruby code result inside the file (e.g., <%= class_name %> becomes UserSignup ). |
<% %> in .tt | Runs Ruby code (loops, logic), but does not output anything in the file directly. Used for logic only. |
params.each | Common Ruby loop used inside .tt to handle multiple initializer arguments or method definitions. |
Meta-programming | Writing Ruby code (in .tt ) that generates other code dynamically, like adding methods or instance variables. |
CLI command structure | How your generator is used: rails generate service ClassName args... . You must parse this inside the generator class. |
Dynamic File Path | Rails lets you control where the final file goes, e.g., app/services/#{file_name}_service.rb . |
File naming conventions | File name and class name must match Rails conventions for autoloading (user_signup_service.rb -> UserSignupService ). |
create_file , copy_file , template | Different ways to generate files in your generator (you mostly use template ). |
File collision handling | Rails asks if you want to overwrite a file if it already exists โ useful for customizing logic. |
Generator testing | You can test generators using Rails::Generators::TestCase (optional for advanced use). |
Reusability | You can reuse your generator across apps or extract it into a gem or engine. |
๐ ๏ธ How to Build a Custom Generator
๐ File Structure
You need to place your generator under:
lib/generators/<your_generator_name>/<your_generator_file>_generator.rb
โ
Step-by-Step Example: rails generate service UserSignup
rails generate generator service
This will generate:
create lib/generators/service/service_generator.rb
create lib/generators/service/USAGE
create lib/generators/service/templates/service.rb.tt
๐งพ Edit service_generator.rb
:
# lib/generators/service/service_generator.rb
require 'rails/generators'
class ServiceGenerator < Rails::Generators::NamedBase
source_root File.expand_path('templates', __dir__)
def create_service_file
template "service.rb.tt", "app/services/#{file_name}_service.rb"
end
end
๐งพ Create the Template:
# lib/generators/service/templates/service.rb.tt
class <%= class_name %>Service
def initialize
# initialize here
end
def call
# business logic here
end
end
โ Run It:
rails generate service UserSignup
โ This creates:
# app/services/user_signup_service.rb
class UserSignupService
def initialize
end
def call
end
end
๐งช 10 Useful Examples
Generator | Command | Purpose |
---|---|---|
Service Object | rails g service UserSignup | Business logic class |
Policy | rails g policy Post | Pundit policy template |
Decorator | rails g decorator Product | Draper or plain decorator |
Job | rails g job CleanupJob | Custom job base template |
Serializer | rails g serializer User | JSON response format |
Query Object | rails g query FetchOrders | Encapsulate DB queries |
Component | rails g component Header | ViewComponent pattern |
Command | rails g command SyncData | For command pattern |
Mailer Template | rails g mail_template WelcomeEmail | Custom mailer views |
RSpec Boilerplate | rails g rspec_service UserSignup | Generator for RSpec test setup |
๐ค 10 Questions & Answers
- Q: Where do custom generators live?
A: Insidelib/generators/
. - Q: Can I create folders and files together?
A: Yes, usingtemplate
orcreate_file
methods. - Q: How to test custom generators?
A: UseRails::Generators::TestCase
. - Q: Can I prompt for input in generators?
A: Yes, usingask
,yes?
, andno?
. - Q: Can I pass arguments like
--api
?
A: Yes, useclass_option
for flags. - Q: How do I overwrite existing files?
A: Useforce: true
or prompt the user. - Q: Can I copy static files too?
A: Yes, usecopy_file
. - Q: Can I reuse logic in multiple generators?
A: Yes, create a shared base class. - Q: Can I nest generators?
A: Yes, you can call other generators. - Q: Can I ship generators in a gem?
A: Absolutely! Generators can be packaged in Rails engines or gems.
Real-World Use Case: SaaS App Generator
Problem: A team creates SaaS apps using the same architecture: service objects, decorators, APIs, and background jobs.
Solution: They created a custom generator set:
rails g service
rails g api_controller
rails g json_serializer
rails g worker
rails g error_handler
โ Result: Any new feature follows the same structure. Junior devs can easily create consistent code.
Custom Rails generators are one of the most powerful tools to automate your workflow. Once set up, theyโll save you hundreds of hours in repetitive coding. Whether you’re building a product, scaling a team, or maintaining architecture โ custom generators help you stay fast and consistent.
Real-World Scenario
SaaS Problem:
Youโre building a scalable multi-tenant SaaS application. You follow a common pattern:
- All business logic goes into
app/services/
- Each service ends in
_service.rb
- Each service has a
call
method, some validations, and logs events - You want new services to be consistently structured
Instead of manually writing the same boilerplate every time, you decide to automate this with a custom generator.
๐ฏ Goal
Build a command:
rails generate service UserSignup
This creates a file:
# app/services/user_signup_service.rb
class UserSignupService
def initialize(user_params)
@user_params = user_params
end
def call
validate!
create_user
log_event
end
private
def validate!
# validation logic here
end
def create_user
# create user logic
end
def log_event
Rails.logger.info("UserSignupService called")
end
end
๐งฑ Step-by-Step Implementation
1. ๐ Create Directory Structure
mkdir -p lib/generators/service/templates
2. โ๏ธ Create Generator File
touch lib/generators/service/service_generator.rb
Content:
# lib/generators/service/service_generator.rb
require 'rails/generators'
class ServiceGenerator < Rails::Generators::NamedBase
source_root File.expand_path("templates", __dir__)
argument :params, type: :array, default: [], banner: "param1 param2"
def create_service_file
template "service.rb.tt", "app/services/#{file_name}_service.rb"
end
end
3. ๐ Create Template
touch lib/generators/service/templates/service.rb.tt
Content of service.rb.tt
:
class <%= class_name %>Service
def initialize(<%= params.join(", ") %>)
<%- params.each do |param| -%>
@<%= param %> = <%= param %>
<%- end -%>
end
def call
validate!
perform
log_event
end
private
def validate!
# add validations here
end
def perform
# main business logic
end
def log_event
Rails.logger.info("<%= class_name %>Service called")
end
end
4. โ Test It
rails generate service UserSignup user_params
This will generate:
# app/services/user_signup_service.rb
class UserSignupService
def initialize(user_params)
@user_params = user_params
end
def call
validate!
perform
log_event
end
private
def validate!
# add validations here
end
def perform
# main business logic
end
def log_event
Rails.logger.info("UserSignupService called")
end
end
What is .tt
in Rails Generators?
.tt
stands for template. It’s used in Rails generators to create files dynamically from a template with embedded Ruby code.
When you use a generator method like template "file.rb.tt", "path/to/file.rb"
, Rails:
- Reads the
.tt
(template) file. - Processes any embedded Ruby code (like
<%= class_name %>
) using ERB. - Writes the final result to the target path (e.g.,
file.rb
).
๐ Available Template Helpers in Generators
Helper | Example Output | Description |
---|---|---|
<%= class_name %> | UserSignup | Converts argument to CamelCase |
<%= file_name %> | user_signup | Converts to snake_case |
<%= params.join(", ") %> | user, password | Join CLI parameters |
<%- ... -%> | (suppresses whitespace) | ERB control tag with no output |
๐ฏ Goal: Dynamic Method Generator via Meta-programming
Weโll extend the service
generator so that:
- You pass method names as arguments
- It generates those methods in the class
- Each method is auto-filled with a
Rails.logger.info
call
๐ Generator Command Example
rails generate service LoggerService user_params log_action log_result
Here:
user_params
is the initializer paramlog_action
,log_result
are dynamic methods to be created
๐ Step-by-Step with Meta-programming
1. โ Modify Generator
# lib/generators/service/service_generator.rb
require 'rails/generators'
class ServiceGenerator < Rails::Generators::NamedBase
source_root File.expand_path("templates", __dir__)
argument :params, type: :array, default: [], banner: "param1 method1 method2"
def create_service_file
template "service.rb.tt", "app/services/#{file_name}_service.rb"
end
def initializer_args
params.select { |arg| arg.include?("_params") || arg.include?("input") }
end
def method_names
params - initializer_args
end
end
2. ๐งพ Update .tt
Template with Meta-programming
class <%= class_name %>Service
def initialize(<%= initializer_args.join(", ") %>)
<% initializer_args.each do |param| %>
@<%= param %> = <%= param %>
<% end %>
end
def call
<%= method_names.map { |m| "#{m}()" }.join("\n ") %>
end
<% method_names.each do |method| %>
def <%= method %>
Rails.logger.info("<%= method %> called with: <%= initializer_args.join(", ") %>")
end
<% end %>
end
3. ๐ Run It
rails generate service LoggerService user_params log_action log_result
๐งพ Output:
class LoggerService
def initialize(user_params)
@user_params = user_params
end
def call
log_action()
log_result()
end
def log_action
Rails.logger.info("log_action called with: user_params")
end
def log_result
Rails.logger.info("log_result called with: user_params")
end
end
Real-World SaaS Use Case
Letโs say youโre working on:
Product: A multi-tenant CRM app
Team Structure: 6 devs, all using the service layer
Problem: Everyone creates their own version of service classes with different structures, naming, and styles.
Solution:
- Create
rails generate service
command - Add other generators:
presenter
,policy
,serializer
,component
- New developers can generate all needed files using consistent templates
Benefits:
- Easily enforce patterns
- Standardized structure
- Faster onboarding
- Avoid manual errors
Learn more aboutย Railsย setup