railcar
Railcar
Three things in one:
- Transpiler -- converts a Ruby on Rails application into a Crystal web application
- Framework -- a Rails-compatible runtime for Crystal, currently covering ActiveRecord and Hotwire, with more to come
- RBS generator -- produces RBS type signatures for existing Rails apps (prototype; planned to leverage Crystal's semantic type inference alongside Rails conventions for comprehensive coverage)
These mix and match. Both Ruby and Crystal input files are supported in the same project, so you can start with a Rails app, generate the Crystal version, then gradually rewrite individual files in Crystal. The pipeline handles both seamlessly.
Status: Early proof of concept -- see Status below.
What it does
Given a Rails app directory, Railcar parses the source code, applies a chain of AST transformations, and generates a Crystal application that compiles and runs.
Source (.rb or .cr)
|
v
Parse (Prism for Ruby, Crystal parser for Crystal)
|
v
Crystal AST (canonical intermediate representation)
|
v
Filter chain (composable transformations)
|
v
Crystal source output (via Crystal.format)
Prerequisites
- Crystal >= 1.10.0
- Ruby with the prism gem installed (
gem install prism) - SQLite3 development headers
- Optional: tailwindcss or
gem install tailwindcss-railsfor styled output
Build
make
This will:
- Locate and build
libprismfrom the prism gem - Compile the
railcarbinary tobuild/railcar
Usage
# Generate a Crystal app
build/railcar /path/to/rails/app /path/to/output
cd /path/to/output
shards install
crystal build src/app.cr
# Generate RBS type signatures
build/railcar --rbs /path/to/rails/app /path/to/output
Test
make test
Downloads a sample Rails blog app and runs the 240-test spec suite.
What works
The blog demo exercises these patterns:
- Models --
has_many,belongs_to,validates(presence, length),dependent: :destroy - Controllers -- CRUD actions,
before_action, strong params,respond_to,redirect_towith flash,renderwith status codes - Views -- ERB to ECR conversion,
link_to,button_to,form_with, partials,render @collection - Routes --
resources, nested resources,root - Tests -- Minitest to Crystal spec, model and controller tests, fixtures with dependency ordering
- Runtime -- ActiveRecord-like ORM with macros, query chaining, validations, associations
Status
The architecture is solid: a clean pipeline from Prism parse through composable AST filters to Crystal output, with a language-agnostic intermediate representation that separates concerns well. The filter chain is the right abstraction -- each new Rails pattern is a small, testable transformer.
The implementation, however, is held together with bailing wire and chewing gum. A non-exhaustive list:
- Hard-coded database -- SQLite only, connection string baked into the generated app
- Hard-coded layout -- a single inline HTML template rather than converting the Rails layout
- Hard-coded import map -- Turbo JS served as a static file, no asset pipeline
- Hard-coded port -- always binds to 0.0.0.0:3000
- Flash as a global hash --
FLASH_STOREis a process-globalHash, not per-request - Form builder in the ERB converter -- ~150 lines of Rails form semantics embedded in what should be a structural pass
content_forsilently dropped -- stripped inside the Turbo Stream filter because it was convenient- Duplicate
model_to_pathlogic -- copy-pasted betweenLinkToPathHelperandButtonToPathHelper
These shortcuts are in place not because the problems are hard, but because they are known to be solvable -- each has a clear path to a proper implementation. The goal at this stage was to prove the pipeline works end to end.
Despite all of this, the proof of concept produces real, observable results: a Rails blog app with models, controllers, views, nested resources, validations, and tests compiles to Crystal and runs.
What needs work
Many common Rails patterns are not yet implemented:
- Models -- scopes, enums, callbacks (beyond stripping), polymorphic associations,
has_one :through, custom validators - Controllers -- filters beyond
before_action, rescue_from, streaming, multi-format responses - Views -- Turbo Streams (currently stripped), Action Text, complex form builders, content_for
- Gems -- Devise, Pundit, Sidekiq, and other common gems each need their own filters
- Infrastructure -- ActionCable/WebSockets, ActiveStorage, ActionMailer, background jobs
- Runtime metaprogramming --
eval,method_missing, dynamicsendcannot be transpiled
The filter architecture is designed so each of these can be added incrementally. See ARCHITECTURE.md for how to contribute.
Relationship to ruby2js
Railcar is a sibling project to ruby2js, which transpiles Ruby to JavaScript. They share the same Prism parser, the same inflector, and the same architectural philosophy: parse source, transform an AST through composable filters, serialize the result. Knowledge from one project transfers to the other.
License
MIT
railcar
- 1
- 0
- 0
- 0
- 2
- about 9 hours ago
- April 9, 2026
Fri, 10 Apr 2026 08:51:06 GMT