Rails Generator Tutorial: Build Custom Code Templates Easily

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?

ProblemSolution with Custom Generator
You repeat similar files in every moduleAutomate it with a generator
Your app uses service objects, decorators, or presentersAdd generators for each pattern
Your team follows a custom folder structureEnforce it using generators
Want to standardize code generation in your teamUse one command for consistency

Custom generators = DRY code + faster development + consistent structure

๐Ÿ“˜ Important Concepts for Beginners โ€“ Custom Rails Generators

๐Ÿงฉ Concept๐Ÿ’ฌ Description
Rails GeneratorA 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.
NamedBaseA helper base class (Rails::Generators::NamedBase) that provides access to file_name, class_name, etc., from your generator name.
.tt fileA template file used by generators. It stands for “template text” and contains ERB (<%= %>) for injecting Ruby code into file generation.
template methodA method in generators that takes .tt and writes a final file after replacing dynamic values.
source_rootTells the generator where to find .tt files. You usually set this to the templates folder.
argumentDefines input passed to the generator (like user_params or method names). These are available as instance variables.
class_nameA helper from NamedBase that converts your input (UserSignup) to class format (UserSignup).
file_nameAnother helper that converts input to snake_case (user_signup).
<%= %> in .ttOutputs Ruby code result inside the file (e.g., <%= class_name %> becomes UserSignup).
<% %> in .ttRuns Ruby code (loops, logic), but does not output anything in the file directly. Used for logic only.
params.eachCommon Ruby loop used inside .tt to handle multiple initializer arguments or method definitions.
Meta-programmingWriting Ruby code (in .tt) that generates other code dynamically, like adding methods or instance variables.
CLI command structureHow your generator is used: rails generate service ClassName args.... You must parse this inside the generator class.
Dynamic File PathRails lets you control where the final file goes, e.g., app/services/#{file_name}_service.rb.
File naming conventionsFile name and class name must match Rails conventions for autoloading (user_signup_service.rb -> UserSignupService).
create_file, copy_file, templateDifferent ways to generate files in your generator (you mostly use template).
File collision handlingRails asks if you want to overwrite a file if it already exists โ€” useful for customizing logic.
Generator testingYou can test generators using Rails::Generators::TestCase (optional for advanced use).
ReusabilityYou 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

GeneratorCommandPurpose
Service Objectrails g service UserSignupBusiness logic class
Policyrails g policy PostPundit policy template
Decoratorrails g decorator ProductDraper or plain decorator
Jobrails g job CleanupJobCustom job base template
Serializerrails g serializer UserJSON response format
Query Objectrails g query FetchOrdersEncapsulate DB queries
Componentrails g component HeaderViewComponent pattern
Commandrails g command SyncDataFor command pattern
Mailer Templaterails g mail_template WelcomeEmailCustom mailer views
RSpec Boilerplaterails g rspec_service UserSignupGenerator for RSpec test setup

๐Ÿค” 10 Questions & Answers

  1. Q: Where do custom generators live?
    A: Inside lib/generators/.
  2. Q: Can I create folders and files together?
    A: Yes, using template or create_file methods.
  3. Q: How to test custom generators?
    A: Use Rails::Generators::TestCase.
  4. Q: Can I prompt for input in generators?
    A: Yes, using ask, yes?, and no?.
  5. Q: Can I pass arguments like --api?
    A: Yes, use class_option for flags.
  6. Q: How do I overwrite existing files?
    A: Use force: true or prompt the user.
  7. Q: Can I copy static files too?
    A: Yes, use copy_file.
  8. Q: Can I reuse logic in multiple generators?
    A: Yes, create a shared base class.
  9. Q: Can I nest generators?
    A: Yes, you can call other generators.
  10. 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:

  1. Reads the .tt (template) file.
  2. Processes any embedded Ruby code (like <%= class_name %>) using ERB.
  3. Writes the final result to the target path (e.g., file.rb).

๐Ÿ›  Available Template Helpers in Generators

HelperExample OutputDescription
<%= class_name %>UserSignupConverts argument to CamelCase
<%= file_name %>user_signupConverts to snake_case
<%= params.join(", ") %>user, passwordJoin 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 param
  • log_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

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top