Skip to content

Nested Generators

Loci generators are designed to be composable. A generator can declare, configure, and execute other generators as part of its own workflow.

There are two primary ways to nest generators:

  1. Declarative composition using the gen macro (recommended).
  2. Direct runtime invocation for manual control.

Declarative Nesting with gen

The gen macro declares a nested generator as a dependency of the parent. This ensures that the nested generator is parsed and validated before execution.

ruby
gen :other_generator

By default, all inputs from the parent are passed through to the child generator.

The skip_flag Option

You can make nested generator optional by using the skip_flag option. This automatically adds a CLI flag based on the provided symbol.

ruby
class MyGenerator < Loci::BaseGenerator
  # Adds a `--skip-model` flag to the CLI
  gen :model_generator, skip_flag: :model
end

If the user passes --skip-model in the terminal, Loci will bypass validation and execution of that specific nested generator.

Customizing Parameters

To modify how parameters are passed to the child, provide a block. This block yields the generator class and the parsed parameters (matched against the parent schema but not yet transformed and type-casted). Note that the child is initialized using keyword arguments.

ruby
gen :other_generator do |g, params|
  g.new(**params, user: params.user.titleize, address: "Static Address")
end

Working with Raw CLI Arguments

If you need full control over the CLI layer (useful when generators follow different positional argument conventions) you can operate on the raw arguments and options. Note that args and opts are passed as first and second positional argument.

ruby
gen :other_generator do |g, _, args, opts|
  args[0] = "Jane Doe"
  opts[:address] = ["Override Address"]

  # Note: Raw option values are always arrays
  # e.g., `--verbose` becomes { verbose: [true] }

  g.new(args, opts)
end

This approach bypasses parameter normalization and interacts directly with the command-line parser.

Multiple Invocations

The same generator can be declared multiple times. Each instance is independently validated, configured, and executed. This is particularly useful for generating sets of related files:

ruby
class Rails::ControllerGenerator < Loci::BaseGenerator
  gen "rails:view", skip_flag: :index do |g|
    g.new template: "index"
  end

  gen "rails:view", skip_flag: :show do |g|
    g.new template: "show"
  end
end

Runtime Invocation

For edge cases where declarative logic isn't sufficient, you can initialize and execute generators manually within your logic.

ruby
class MyGenerator < Loci::BaseGenerator
  def call
    AnotherGenerator.new(name: "John Doe").call
  end
end

The Runtime Invocation Trade-off

The primary difference lies in when errors are caught:

  • Declarative (gen): Performs a "pre-flight" check. Loci validates all nested parameters before any execution begins. If something is wrong, nothing runs.
  • Runtime (.new): Validation is deferred. The parent generator starts running immediately and only fails when it hits the nested call. This can leave your generator in a "half-finished" state if an error occurs mid-stream.