Skip to content

Latest commit

 

History

2,826 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Roundhouse logo — a turntable at the center, six colored tracks radiating outward

Roundhouse

Rails as a specification; deployment is a build flag.

Roundhouse reads Ruby source — specifically, Rails applications — and produces standalone projects in other target languages. The deployment target (Rust or Swift binary, TypeScript bundle, Crystal or Go service, Elixir OTP app, Kotlin/JVM or C#/.NET service, Python project, browser bundle, or Spinel-compiled Ruby) becomes a compiler flag rather than a runtime choice.

A roundhouse is the circular hub in a rail yard where engines rotate and route onto different tracks. That's the pipeline shape: one Ruby source at the center, analyzed and dispatched to one of N target tracks.

For the case for doing this at all — the constraints that push successful Rails apps off CRuby, and the option value of preserving the choice — see WHY.md.

What it does

Three things, from one binary and one analysis. Each has a door in the user guide.

Analyze. No type annotations are involved anywhere. Rails was already typed: has_many :comments is a type declaration, and the framework's conventions carry type information that was simply never written down. Roundhouse recovers it by whole-program inference — which class an association returns, which columns a model has and what they deserialize to, whether a find_by came back nil — from unmodified source, without booting the app or touching a database. A pass over Mastodon (1,173 files, all 337 controllers, HAML views included) takes about 1.5 seconds. That inference is a product in its own right: roundhouse check for a terminal or a CI gate, an LSP server for your editor, an MCP server for your agent — types, nil-safety, static N+1 findings, and the full request trace for any action — and an in-browser IDE that analyzes a folder on your disk without uploading it. Static, deep and annotation-free is a cell of the Ruby tooling space nobody else occupies: ruby-lsp is static but stops at names; ruby-lsp-rails and Tidewave are deep but need a running app; Sorbet and Steep are static and deep but you pay in annotations. → check · editor · agent · IDE

Transpile. The same analysis fed to a dozen emitters: a standalone project in Rust, Go, TypeScript, Crystal, Elixir, Kotlin, Swift, Python, C#, or Ruby, with its own tests and no Rails at runtime. The way we know the output is correct is a conformance oracle: the same URL fetched from Rails and from each target must produce the same response — emitted tests, a differential compare against live Rails (DOM node for DOM node, JSON value for value), and browser end-to-end tests for what a static diff can't reach — and every target on the list passes it on every push. → --target · targets · what of Rails comes through · verifying

Compile. The Ruby shape compiled ahead of time to one native binary by Spinel, Matz's AOT Ruby compiler: one executable, one SQLite file, no interpreter. Among the compiled targets it has the closest behavior to Rails by a distance, and will for the foreseeable future, because it runs the framework runtime itself rather than a translation of it. Basecamp's Campfire runs this way — every page and every cable frame compared against live Rails on every push, and a Docker archive you can run in minutes. → Spinel

Get it

curl --proto '=https' --tlsv1.2 -LsSf \
  https://github.com/rubys/roundhouse/releases/latest/download/roundhouse-installer.sh | sh
roundhouse --version
roundhouse check --continue /path/to/your/rails/app

Releases are dated snapshots with binaries for macOS on Apple silicon and Linux x86-64; RELEASES.md says what each one contains. The binaries cover most needs, and cargo build --release --bin roundhouse from a checkout is always there for the rest — docs/guide/install.md has both paths and the prerequisites.

See it for yourself

  • IDE — the analyzer in a browser tab, preloaded with the Rails Guides store, the blog, Lobsters, Campfire and Mastodon; open folder… for your own app.
  • Campfire — the compiled product, as a Docker archive.
  • Browse — what every emitter produces from the blog fixture, updated on each push.
  • Bench — throughput, memory and latency across the live targets on a fixed box, against Rails as it ships.
  • Demo — the fixture every target is built and tested against, and its three test layers.

Why it is fast

Rails is, operationally, an interpreter for your application — routes, associations, validations and templates are data it consults on every request. Every decision whose answer cannot differ between requests, Roundhouse makes once at transpile time; only the per-request residue survives to runtime. On the blog fixture, serving the HTML index on a fixed Linux x86 server (July 2026 round):

configuration req/sec
Rails on CRuby+YJIT 326
Rails on JRuby 1,066
Roundhouse emit on CRuby+YJIT 3,292
Roundhouse emit on JRuby 24,172

Stripping the interpretive layers is worth ~10× on the same interpreter; the static, monomorphic Ruby that remains is the input the JVM JIT was built for, worth a further ~7×; the compiled targets go further still. These are ratios from a CPU-bound microbenchmark of a small fixture, and the live numbers with their environment capture are on the bench page. The long-form versions:

Documentation

Using it: the user guide — one page per door above, starting at install — and RELEASES.md.

Working on it: DEVELOPMENT.md (build, test, the bin/rh workflow runner, debugging tools, repo map), AGENTS.md (the invariants not to break), and docs/ — the architecture: the compiler's inputs, the pipeline (analyze, lower, emit, runtime, verification), and the working plans. BETS.md is why this attempt is shaped differently from its predecessors; WHY.md is why do it at all.

Prior art

  • railcar — the Crystal-based predecessor; taught us which bets were worth keeping and where the shape needed to change.
  • ruby2js — transpiles Ruby to JavaScript; originator of the filter/escape-hatch pattern for per-app transformations.
  • Juntos — ruby2js extension that transpiles entire Rails apps; validated the multi-target ambition against Basecamp's Writebook.

Contributing

Issues and discussion are welcome. Architecture is still forming — a quick conversation before a PR is usually the most helpful path.

License

Dual-licensed under either of

at your option.

runtime/spinel/tep/ carries code that began as tep 0.8.1 by Ori Pekelman, MIT-licensed; see runtime/spinel/tep/NOTICE. That directory ships into every emitted spinel tree, under runtime/tep there.

About

Rails as a specification; the deployment target is a build flag

Topics

Resources

Stars

105 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages