# Migrating from Rails

The concept map for a Rails app. The runbook — phases, exit conditions, stop conditions — is
[Migrating an existing app](/migrating-an-existing-app); this page says what each Rails term
becomes. `As of 2026-11`, against Rails 8 ([guides.rubyonrails.org](https://guides.rubyonrails.org/)).

## Concept map

| Rails | Ultimate |
|---|---|
| `config/routes.rb` + a controller action that renders | a route file: [Routes and render modes](/routes-and-render-modes) |
| a controller action that writes | an `action`: [Actions](/actions) |
| an Active Record model | an `entity()`: [Entities and migrations](/entities-and-migrations) |
| a scope, a `where` chain | a `query`: [Queries and live queries](/queries-and-live-queries) |
| `db/migrate/*.rb`, `schema.rb` | `x db gen "<name>"`, then `x db migrate` |
| `rails generate` | `x g <kind> <name>` ([CLI reference](/cli-reference)) |
| Active Job, Sidekiq | a `job`: [Jobs and workflows](/jobs-and-workflows) |
| `whenever`, `sidekiq-cron` | a `task`: [Scheduled tasks](/scheduled-tasks) |
| Devise, `has_secure_password` | [Auth](/auth) |
| Pundit, CanCanCan | a `policy`: [Policies and authz](/policies-and-authz) |
| Action Cable, Turbo Streams | a live query: [Realtime](/realtime) |
| Action Mailer | [Mail](/mail) |
| Active Storage | [Storage and uploads](/storage-and-uploads) |
| ActiveAdmin, Administrate | [Admin dashboard](/admin-dashboard) |
| `config/locales/*.yml` | one flat catalog per locale: [I18n](/i18n) |
| RSpec, Minitest | [Testing](/testing) |
| `bin/rails server` | `x dev` |
| `bin/rails test` + CI | `x verify` |

## What does not map

| Rails | Why |
|---|---|
| Ruby and its gems | Bun only. Nothing runs Ruby; a gem's behaviour is rewritten or bought as a service |
| MySQL, SQLite | Postgres only, by design: [Home](/docs) |
| ERB, Hotwire, Stimulus | pages render with SolidJS; interactive parts are islands ([Routes and render modes](/routes-and-render-modes)). No converter |
| model callbacks (`before_save`, `after_commit`) | no implicit hooks: the write is an `action` or a `mutator` that says what it does ([Actions](/actions)) |
| SAML (`devise_saml_authenticatable`) | not shipped: put an OIDC bridge in front ([Auth](/auth)) |

## Schema adoption

Rails ids are `bigint` sequences and timestamps are `created_at` / `updated_at`. Phase 1 of the
runbook declares the existing tables as entities with the physical-name overrides and type map it
gives, then `x db gen` until `x verify --only drift` agrees: [Migrating an existing app](/migrating-an-existing-app).

## Identity cutover

| Rails stores | Branch |
|---|---|
| `password_digest` (`has_secure_password`) or Devise's `encrypted_password` — bcrypt by default | **proceed**: verified natively, rewritten as argon2id on first login ([Migrating an existing app](/migrating-an-existing-app)) |
| the session: an encrypted cookie keyed by `secret_key_base` | bridge it with your own verifier behind `configureAuthenticator()`, or keep the Rails login until every user has signed in once ([Auth](/auth)) |
