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:
- Models gain associations
(
has_many :comments, dependent: :destroy,belongs_to :article), validations (presence,length: { minimum: 10 }), and broadcasting declarations. - Nested routes —
resources :articles do resources :comments, only: [:create, :destroy] end, withroot "articles#index". - Strong parameters using the modern
params.expect(...)form. - Views and partials — the index renders the
_articlepartial as a collection; the show page renders@article.commentsand aform_withcomment form. View helpers likedom_id,pluralize,truncate,link_to, andbutton_toall appear. - Seeds and fixtures — three articles (with 2, 1, and 0 comments) so every emitted target starts from identical data, used by both the demo and the tests.
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.
| Layer | What it checks | Against |
|---|---|---|
| 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.