Roundhouse

The real-blog fixture — what every target is built and tested against

Every emitted project on this site comes from a single Rails application: fixtures/real-blog. It is a blog — the quintessential Rails demo — deliberately kept small but stretched to touch the framework features that make a transpiler interesting: associations, validations, nested routes, strong parameters, ERB partials and collection rendering, JBuilder, flash, and real-time updates over WebSocket.

The fixture is not hand-written and checked in. It is generated by a script — create-blog (download) — that runs the standard Rails generators and then applies a handful of edits. Starting from rails new + rails generate scaffold keeps the app honest: it is recognizably the output a Rails developer would produce, not a shape contrived to be easy to transpile.

How it's built

create-blog scaffolds two resources and wires them together. The skeleton:

# A fresh Rails app with Tailwind for styling
rails new blog --skip-git --skip-docker --css tailwind
cd blog

# Two scaffolds: articles, and comments nested under them
rails generate scaffold Article title:string body:text
rails generate scaffold Comment article:references commenter:string body:text

rails db:create

From there the script edits the generated files into a coherent app — the parts a transpiler has to understand:

The full script is the source of truth — read or download it at create-blog. In the repo, bin/rh fixture runs it and materializes the result under fixtures/real-blog/.

Modernized Rails

The blog is not a 2010-era CRUD demo. It uses the current Rails front-end stack, which is exactly the part that is hardest to carry across languages:

Tailwind CSS

Generated with --css tailwind. The published archives ship a compiled static/assets/tailwind.css, so a downloaded target serves a fully styled app with no build step.

Turbo Streams

Models declare broadcasts_to, and views subscribe with turbo_stream_from. Creating a comment inserts a row into the page without a full reload; creating or destroying an article prepends/removes it on the index — all driven by server-rendered stream fragments.

Action Cable

Those broadcasts ride a WebSocket. A comment added in one browser appears live in another viewer's tab. Every target boots an Action Cable-compatible server alongside HTTP, so the real-time behavior is preserved, not stubbed.

How it's tested

The same fixture is verified three different ways. Together they pin down that an emitted target is not just well-typed, but behaves like Rails.

LayerWhat it checksAgainst
Model & controller tests Minitest model and controller tests (validations, associations, CRUD actions, redirects, 422 on invalid params) are emitted alongside the app and run in each target language. Fixed expected values
Compare tests tools/compare/ fetches the same URL from Rails and from a roundhouse target, canonicalizes both DOM trees, and diffs them. A page that renders differently is a bug. Live Rails reference
E2E tests Playwright specs drive a real browser through the dynamic behavior a static DOM diff can't reach: Turbo Stream comment inserts, Action Cable broadcasts, validation re-renders, and computed Tailwind styles. Fixed expected values

The model and controller tests travel with the source — they are part of what create-blog writes, so each target compiles and runs its own translated test suite. The compare and E2E layers are CI invariants: the compare-* jobs gate the Pages deploy, so any drift between a target and Rails fails the build before it can ship to this site.

See it

Browse the generated output for every target · benchmark results across the live targets · download the generator script · source on GitHub.