Most apps need no configuration. Roundhouse reads the app as it is, and
an app without a roundhouse.yml gets the default behavior.
Add the file at the app root, next to Gemfile, when you need to tell
Roundhouse something it cannot learn from the source. The file has two
keys:
test_paths:
- test/unit
extensions:
- config/roundhouse
test_pathsadds test folders to the default list.extensionslists directories that describe the app's gems and class-body DSLs.
Roundhouse reads the file during ingest, so every tool sees it:
roundhouse check, the editor, the
MCP server, the in-browser IDE (from the folder you
open), and roundhouse --target.
Errors
A mistake in roundhouse.yml or in an extension file stops ingestion.
Roundhouse does not ignore the entry or guess what it meant. Unknown keys
are errors too, so a typo fails loudly:
$ roundhouse check .
roundhouse-check: ingest failed: parse error in ./roundhouse.yml: unknown field `extension`, expected `test_paths` or `extensions`
check exits 2 on these errors. --continue does not get past them,
even though the hint line suggests it.
test_paths
Roundhouse reads every .rb file recursively from these folders by
default:
test/modelstest/controllerstest/helperstest/channelstest/lib
test_paths adds folders:
test_paths:
- test/unit
- quality/specs
test_paths adds folders. It does not replace the default folders. Each
value must name a literal directory relative to the app root. Roundhouse
does not expand globs, environment variables, or ~.
Roundhouse rejects a configured path with a symbolic link. It does not
follow symbolic links below selected folders.
Roundhouse reads test/test_helper.rb, test/test_helpers/, and
test/fixtures/ outside this folder list.
Transpiled projects include tests from the default folders and these folders.
extensions
Roundhouse models Rails and a set of common gems. Project extensions describe other gems and class-body DSLs that Roundhouse does not know about. The descriptions live in the app's own tree, so the compiler does not have to carry them.
When you need one
A gem's value object fails at every use. A gem adds a class-body macro that turns columns into value objects:
class Invoice < ApplicationRecord
price_column :amount, currency: :usd
def label = amount.currency
end
Roundhouse types amount from the schema, so each use of the value
object is an error:
app/models/invoice.rb:4:15: error[send_dispatch_failed]: no known method `currency` on Float?
An attribute_macros entry makes the attribute untyped
and reports one error at the macro instead:
app/models/invoice.rb:2:3: error[unsupported]: price_column not supported (all targets): attribute macro on `Invoice` from a project extension: its attributes read untyped, and no target lowers the call
The error remains because no target translates the macro yet, but it now names the cause once, rather than one error for each use.
The ruby tree needs a gem it does not load. The ruby and
jruby targets carry many of the app's gems, but not every gem is
required at boot or declared in the emitted Gemfile.
gems.yml tells those targets how to load or declare a gem,
or records that the gem cannot load without Rails. Its facts can also
select a locked gem's RBI for type information. An RBI supplies types,
not executable code. Without a selected RBI, a constant from a gem
Roundhouse does not model can still produce an error:
app/services/report.rb:3:5: error[unsupported]: constant not supported (all targets): WidgetEngine::Thing — unmodeled gem `widget_engine`; emitted constant availability is unverified
Directories
List extension directories in roundhouse.yml:
extensions:
- config/roundhouse
Each entry names a literal directory relative to the app root, as with
test_paths. It must not contain .., an absolute path, or a symbolic
link in the directory path. Directories load in the listed order.
The same directory must not appear twice.
A directory can hold gems.yml and macros.yml. Both files are
optional. Each file can be a symbolic link, including a link to a file
outside the app root, so several apps can share one description of a
gem. Roundhouse reports an error if it cannot read a linked file.
gems.yml
lock_gated_requires:
- {require: "widgets/all", gem: "widgets"}
rails_dependent:
- {gem: "widget-engine", why: "requiring it loads Rails framework files"}
runtime_gems:
- {constant: "Widget", gem: "widgets"}
- {any_text: ["Widget::Cache."], gem: "widget-cache"}
source_gems:
- {gem: "pundit"}
lock_gated_requires: when the app'sGemfile.lockresolves the gem, therubyandjrubytrees require the path at boot. Registry gems use the locked version. Git gems keep the locked URL and revision. PATH gems remain source inputs; this setting does not load them natively.rails_dependent: requiring the gem loads Rails, so compiled projects do not load it natively. References to its constants stay errors, including constants made known by an RBI. The error includes the gem name and thewhytext. App-owned constants keep their own definitions. The Ruby-core exemption applies to exact constants such asString, not to all constants inside their namespaces.runtime_gems: therubyandjrubytrees declare the gem in their Gemfile when a file underapp/names the constant or contains one of the texts. Git gems keep their source and revision. This setting does not require the gem; use an application require orlock_gated_requires.source_gems: the gem's own Ruby is compiled with the app, as aPATHgem's is. Roundhouse reads the versionGemfile.lockresolves from where Bundler installed it (BUNDLE_PATHin.bundle/config, thenGEM_HOMEandGEM_PATH), and takes only the filesrequire "<gem>"loads from itslib/: literalrequire,require_relativeandautoloadcalls, followed fromlib/<gem>.rb. An autoloaded file is compiled whether or not the app names its constant. Generators and test helpers the entry file does not load stay out. What the code needs that Roundhouse does not support is reported as for the app's own code. The census lists the gem assource. Runbundle installbeforeroundhouse check; a gem that is not installed, not in the lockfile, or also named by another fact is an error.
Extension gem facts also select the locked gems' Tapioca RBIs under
sorbet/rbi/gems/. This includes gems classified as infrastructure or
standard-library gems. An RBI supplies types and ownership, not executable
code. A gem in rails_dependent stays refused even when its RBI declares
the referenced class, module, or value. Other targets refuse constants
that depend on a native extension gem instead of emitting missing classes.
Roundhouse records extension boot loads on each app. A lockfile dependency
alone does not establish that a gem loads. A runtime_gems row does not
become a boot require.
For Git-backed native gems, run the emitted project's bundle install.
At boot, Roundhouse activates the locked checkouts under
<gem home>/bundler/gems/, including Git-backed dependencies, before the
guarded requires. It reports an error if source metadata or a checkout
is missing, or if a published copy is already active. It never replaces
a locked Git source with a published gem of the same name.
The Gemfile also preserves Git glob and submodules settings.
The runtime uses the locked glob to find each gemspec.
A guarded require stays quiet when the optional feature itself is absent. If a present feature fails to load an internal dependency, the runtime reports both the requested feature and the load error on standard error.
macros.yml
attribute_macros:
- {name: price_column, positional: true} # price_column :a, :b, opt: 1
- {name: bag_columns, hash_key: attributes} # bag_columns attributes: [:a, :b]
attribute_macros: the attributes a macro names read and write as
untyped. With positional, the names are the macro's leading symbol
arguments. With hash_key, they are the array under that keyword. No
target lowers the macro itself, so each call is reported as an error at
its declaration. This replaces a dispatch error at every place the value
object is used.
Configured attribute macros also apply in a concern's included do
block. Roundhouse retains direct statements from ActiveSupport::Concern
blocks and inserts them into each including model before analysis.
Each model gets the macro's attribute types and its declaration error.
A concern that no model includes does not change attribute types or
produce macro declaration errors. This does not add general class-body
execution or support for class_attribute on models.
Rules
Extension rows are added after Roundhouse's own rows and never replace
them. Unknown keys, malformed rows, a missing directory, and a
rails_dependent gem that another fact loads or that Roundhouse models
all stop ingestion with an error that names the file. The types and
overlay entries are reserved and rejected until they are implemented.
The CLI and browser use the same built-in gem tables to validate extensions.
Extension facts belong to the app that lists them. An app that lists no extensions behaves as before.
This page is docs/guide/roundhouse-yml.md in the repository; edits are welcome there.