# Rails + Hotwire

Database-backed products with HTML, accounts and jobs close together.

Language: Ruby | Reviewed: 2026-10-10 | Evidence: documentation-reviewed

Core guidance checked: 2026-10-13. See /research-freshness.json for review scope. These recipes and selected dependency combinations need qualification in the actual project.

## Application role
Components: web
UI: server
Deployment: server

## Use cases
- SaaS
- Business CRUD
- Collaboration and workflow products

## Fit
- An integrated monolith and server-owned UI fit the product.
- A small team benefits from stable conventions and limited integration work.

## Choose something else when
- The core is an offline-first canvas or requires extensive browser-owned state.
- Hard CPU/latency constraints justify a specialist service.

## Agent workflow
- Read README, dependency manifests, lockfiles, runtime pins and the maintained project workflow before changing anything. Preserve an existing stack unless reconsidering it is part of the request.
- Use this guide to select tools by requirement. Resolve compatible versions from official and installed-version documentation; record the actual lockfile and runtime versions.
- Create a plan for schema changes, identity, authorization, external effects and deployment. A library choice does not complete its integration.
- Run relevant checks after configuration. Exercise the real interface, failure paths and packaged artifact on the intended target. Report commands, versions, results and untested behavior.
- On failure, retain the command and diagnostic, fix the failing prerequisite and resume there. Do not regenerate over existing work or suppress failures to make a check green.

## Best practices
- Keep Turbo and Stimulus focused on interface behavior; put authorization in explicit policies.
- Run migrations and exercise the generated authentication flow before building product features around it.
- Treat Solid Queue workers as operated processes. Make retries safe and verify recovery after interruption.
- Use system tests for the critical browser journey and rehearse backups and restore before shipping.

## Tools

### UI: ERB + Turbo + Stimulus

Server-rendered HTML and selective JavaScript limit duplicated UI state.

Limits and integration: Do not force Hotwire onto rich offline editors.


- https://hotwired.dev/

Alternatives: React client when browser-owned state dominates; retain Rails API

### Database and migrations: PostgreSQL + Active Record migrations

Relational constraints and conventional migrations fit business data.

Limits and integration: Use unique/foreign-key constraints; assess locks and use expand/contract changes for rolling deploys.


- https://guides.rubyonrails.org/active_record_migrations.html

Alternatives: SQLite for deliberately single-host deployments

### Authentication: Rails authentication generator

Generated local sessions plus explicit per-resource authorization.

Limits and integration: Generator is a starting point; signup, authorization and account lifecycle require product decisions.


- https://guides.rubyonrails.org/getting_started.html
- https://github.com/varvet/pundit
- https://github.com/heartcombo/devise

Alternatives: Devise when its additional authentication modules are needed

### Authorization: Pundit policy classes

Generated local sessions plus explicit per-resource authorization.

Limits and integration: Generator is a starting point; signup, authorization and account lifecycle require product decisions.


- https://guides.rubyonrails.org/getting_started.html
- https://github.com/varvet/pundit
- https://github.com/heartcombo/devise

Alternatives: Existing resource-level policy implementation; keep authentication separate

### Jobs: Active Job + Solid Queue

Database-backed jobs avoid a mandatory separate broker.

Limits and integration: Configure worker capacity, retry limits and idempotency; separate queue DB does not guarantee atomic app-write/enqueue.


- https://guides.rubyonrails.org/active_job_basics.html

Alternatives: Another Active Job backend only for measured operational needs

### Tests: Minitest + Rails integration/system tests

Exercise authorization, database constraints and essential browser flows.

Limits and integration: Browser tests need their driver/browser; unit tests alone miss UI and transaction races.


- https://guides.rubyonrails.org/testing.html

Alternatives: Keep RSpec in an established RSpec repository

### Lint and security: RuboCop + Brakeman + bundler-audit

Style, application scanning and dependency advisories cover different risks.

Limits and integration: Scans do not establish security; test tenant isolation and unsafe uploads. bundler-audit has GPL-3.0 licensing.


- https://guides.rubyonrails.org/getting_started.html
- https://guides.rubyonrails.org/security.html
- https://github.com/rubysec/bundler-audit

Alternatives: Preserve existing compatible project tooling

### Deployment: Kamal + generated Dockerfile + PostgreSQL backups

Portable container deployment with application-owned configuration.

Limits and integration: Requires registry/server credentials and persistent data strategy; backups and restore drills remain operator work.


- https://kamal-deploy.org/docs/installation/
- https://guides.rubyonrails.org/getting_started.html

Alternatives: Managed container hosting when server operations are undesirable

### Agent tooling: Generators + routes/schema/test feedback + version-matched Rails guides

Conventions make edits easier to locate and verify.

Limits and integration: Agent productivity is an inference; no cross-framework coding-success benchmark established.


- https://rubyonrails.org/ai

Alternatives: Add only a reviewed MCP integration with a concrete need

### Licensing and hosting: Rails MIT; self-hostable

No mandatory proprietary Rails hosting service.

Limits and integration: Dependency licenses remain separate; do not infer all gems are MIT.


- https://github.com/rails/rails/blob/main/MIT-LICENSE

Alternatives: Paid infrastructure/support when operationally useful

### Transactional email: Action Mailer

Framework mailers integrate templates and queued delivery

Limits and integration: Configure provider transport and sender; use deliver_later when delivery must be queued; test content and failure behavior. Provider acceptance is not inbox delivery.


- https://guides.rubyonrails.org/action_mailer_basics.html

Alternatives: Preserve an existing compatible project choice

### Uploads and object storage: Active Storage

Framework attachments support external storage and direct uploads

Limits and integration: Run the installed-version Active Storage generator/migrations; configure private storage, authorization, limits and cleanup. Public blob URLs and user-supplied files need an explicit access policy.


- https://guides.rubyonrails.org/active_storage_overview.html

Alternatives: Preserve an existing compatible project choice

### Realtime transport: Action Cable

Rails channels integrate WebSocket messages with application code

Limits and integration: Configure chosen cable adapter and connection/channel authorization; test reconnect, revoked membership and host proxy behavior. Broadcast is not durable event history.


- https://guides.rubyonrails.org/action_cable_overview.html

Alternatives: Preserve an existing compatible project choice

## Architecture and developer workflow

Rails supplies a conventional MVC application and Active Record persistence. Turbo Drive, Frames and Streams support a server-rendered HTML interface with incremental browser updates. Recommendation: keep domain writes on the server and introduce browser-owned state only for interactions that need it; a framework rewrite is not a prerequisite for fixing an inefficient query or incorrect queue contract.

Version scope: Rails guides showing Rails 8.1 examples; current Turbo handbook. These describe target capabilities, not the source application's installed versions. Detect Gemfile.lock, Ruby version and configured adapters before choosing a move.
Reviewed: 2026-10-11 | Evidence: documentation-reviewed

### Architecture boundaries

- Recommendation: establish one authoritative domain writer and one schema migration runner per shared database during coexistence. Route a stable tenant or feature boundary, and keep the old application able to read new writes until rollback expires.
- Active Job is the application interface; Solid Queue is the Rails 8 default backend. Inspect the application's actual adapter before inferring a Redis dependency. Separate workers, recurring task configuration and domain writes are distinct responsibilities.
- Recommendation: treat Turbo stream DOM targets, Action Cable channel identifiers, job serialization and session cookies as separate contracts. Sharing a datastore does not prove any of their compatibility.

### Tools and feedback

- In an existing checkout, read Gemfile.lock and environment configuration before adapting generators. In a disposable feature prototype, bin/rails generate model Product name:string and bin/rails db:migrate demonstrate the conventional model/migration path; review generated schema changes before applying them.
- bin/rails generate job guests_cleanup creates an Active Job class; configure its selected backend and worker process separately. Recipe only: use bin/jobs start for a configured Solid Queue application, then exercise enqueue, failure and recovery against its real queue database.
- bin/rails console is useful for inspecting application objects. Recommendation: capture authorized and unauthorized request fixtures, mixed-version writes and browser stream behavior as acceptance checks rather than treating successful boot as migration qualification.
- For a new local-account prototype, bin/rails generate authentication creates User and Session models plus login surfaces. Review account lifecycle and resource authorization separately; do not run it over an established authentication system without an intentional migration design.

### Translations to avoid

- Avoid calling a queue-adapter configuration change a backlog migration: the old backend retains work and Active Job can serialize GlobalID and custom types that need the original Ruby classes and database semantics.
- Avoid claiming atomic domain-write-and-enqueue from a separate Solid Queue database. After-commit enqueue prevents premature execution but is not a transaction across two stores. Recommendation: use a durable outbox when a crash between commit and delivery must be recoverable.
- Avoid the Action Cable async adapter for distributed production delivery; the guide restricts it to development/testing and a single process. Avoid replacing session or lock data with a disposable cache just because both use Redis.

### Architecture sources

- [Getting Started with Rails](https://guides.rubyonrails.org/getting_started.html): Rails 8.1 examples, MVC/Active Record, model and authentication generators, migration and console commands.
- [Active Record Migrations](https://guides.rubyonrails.org/active_record_migrations.html): Schema migration files, database constraints, reversal and rollback semantics.
- [Active Job Basics](https://guides.rubyonrails.org/active_job_basics.html): Active Job serialization, Solid Queue default and process roles, bin/jobs, separate database default, enqueue timing, recurring tasks and retry handling.
- [Securing Rails Applications](https://guides.rubyonrails.org/security.html): CookieStore, signing/encryption rotation, replay/fixation and session expiration considerations.
- [Action Cable Overview](https://guides.rubyonrails.org/action_cable_overview.html): Cable connections/channels, adapter configuration, async locality, Solid Cable, origins and server deployment.
- [Caching with Rails](https://guides.rubyonrails.org/caching_with_rails.html): Rails cache interfaces, SQL cache lifetime, Solid Cache and alternative stores.
- [Turbo introduction](https://turbo.hotwired.dev/handbook/introduction): Drive, Frames and Streams extend server-rendered HTML navigation and updates.
- [Turbo Streams](https://turbo.hotwired.dev/handbook/streams): DOM target operations and stream transport options.
- [Sidekiq Best Practices](https://github.com/sidekiq/sidekiq/wiki/Best-Practices): Jobs must be idempotent; execution is at least once rather than exactly once.

## Transition options

Proposed designs and checks; these are not executed compatibility results.

### Data and schema

**Inspect the source:** Inventory table names, primary keys, timestamps, enum values, constraints, callbacks and the application that currently owns migrations; Active Record migrations define schema evolution but do not describe all application side effects.

**During coexistence:** Recommendation: keep the existing writer authoritative while the target reads representative data. Add backward-readable fields first; transfer one domain boundary explicitly. Keep a single migration runner rather than allowing two framework migration histories to modify the same tables independently.

**Target design:** Rails Active Record over the selected PostgreSQL schema, with reviewed database constraints and migrations. Recommendation: remove legacy fields only after old code and rollback readers have retired.

**Rollback:** Recommendation: route writes back only while the old reader understands new records. Preserve additive schema changes; rehearse reversible migrations separately and reconcile irreversible data changes.

**Benefits and prerequisites**

- Recommendation: conventional migrations and model access can concentrate schema work in one maintained application. Requires: A named schema owner, compatible value semantics and staged writer transfer. Tradeoffs: Shared tables constrain both deployments; deleting or rewriting data may require reconciliation rather than migration reversal.

**Checks before transfer**

- Create in the old application and read in Rails, then reverse the direction after writer transfer; compare IDs, timestamps and nullable values.
- Run the proposed migration against a disposable copy; verify both deployed versions can still read and update a fixture, including unique and foreign-key failures.

- https://guides.rubyonrails.org/active_record_migrations.html
- https://guides.rubyonrails.org/getting_started.html

### Jobs and schedules

**Inspect the source:** Record adapter, job class, argument serializers/GlobalIDs, queue names, retries, scheduled work, failed work and running leases. Rails exposes these through Active Job while each backend supplies execution behavior.

**During coexistence:** Recommendation: retain old consumers for old payloads; route a new job category to a new queue/backend. Transfer its producers and recurring-task owner together. Drain or deliberately transform ready, scheduled, retry, failed and in-flight work; do not run both schedulers for the same business operation.

**Target design:** Active Job with configured Solid Queue or the retained qualified adapter. Same-database enqueue may join a domain transaction; a separate queue database needs an explicit delivery design. Recommendation: require business idempotency even when job uniqueness exists.

**Rollback:** Recommendation: stop new producers before restoring old routing; retain both consumers through the rollback window and reconcile completed external effects before re-enqueueing.

**Benefits and prerequisites**

- SQL-backed job persistence may allow retirement of a dedicated broker. Requires: All broker uses inventoried and retired; workers, queue schema, failure monitoring and backlog reconciliation in place. Tradeoffs: Database work and queue operations remain operational responsibilities; retaining Redis for another capability yields no service-count reduction.

**Checks before transfer**

- Replay captured old serializer fixtures using the retained consumer; a deleted GlobalID target must follow the intended failure policy.
- Rollback a domain transaction and confirm no executable job escapes it; kill after an external effect before acknowledgement and verify a retry cannot repeat the business effect.
- Account for every backlog state and observe only one scheduled enqueue for the transferred category.

- https://guides.rubyonrails.org/active_job_basics.html
- https://github.com/sidekiq/sidekiq/wiki/Best-Practices

### Identity and authorization

**Inspect the source:** Distinguish the application's generated Session model from Rails session storage. Capture cookie name, serializer, signing/encryption configuration, expiration, account IDs, logout/revocation and per-resource authorization.

**During coexistence:** Recommendation: keep a shared identity authority or a documented session exchange endpoint. If cookies are shared, qualify the complete cookie format and rotation rules; route stable users and retain the old decoder for the agreed window.

**Target design:** An intentionally selected Rails authentication implementation with application authorization. CookieStore stores session data on the client; account/session lifecycle rules must be designed separately.

**Rollback:** Recommendation: preserve old account IDs and accepted session formats until routing stabilizes; coordinate revocation and key rotation so restoring traffic cannot revive invalidated sessions.

**Benefits and prerequisites**

- Recommendation: coherent account and session ownership can preserve login continuity during feature migration. Requires: Verified identity mapping, cookie/exchange fixtures and shared revocation behavior. Tradeoffs: Compatibility code and key rotation increase maintenance; planned reauthentication is an alternative when continuity is unnecessary.

**Checks before transfer**

- Authenticate through the old path and visit the new route; then logout, expire or revoke the session and confirm both paths deny access.
- Replay a modified cookie, a stale cookie and a valid session from another tenant against a protected resource; each unauthorized operation must be rejected.

- https://guides.rubyonrails.org/security.html
- https://guides.rubyonrails.org/getting_started.html

### Realtime transport

**Inspect the source:** Capture channel names, connection authentication, subscription commands, DOM target IDs, Turbo stream actions and reconnect behavior. Turbo Streams can arrive over WebSockets, SSE or request responses; Action Cable owns its own client/server protocol.

**During coexistence:** Recommendation: keep the current realtime endpoint for old clients; bridge versioned domain events to the selected target renderer. Assign one event publisher per committed change and route a room/user cohort including reconnects, rather than randomly splitting individual socket requests.

**Target design:** Turbo streams over the selected transport, with Action Cable using a configured shared adapter when multiple server processes must communicate. Solid Cable is database-backed; Redis remains a documented alternative.

**Rollback:** Recommendation: preserve the old event renderer/endpoint and backward-readable stream payloads until the cohort can reconnect there; reconcile already-delivered state rather than blindly rebroadcasting.

**Benefits and prerequisites**

- HTML updates can reuse server-rendered partials. Requires: Stable DOM targets, authorized subscriptions and compatible event-to-view rendering. Tradeoffs: A new socket protocol still needs client changes or a bridge; reconnection recovery is an application acceptance requirement.

**Checks before transfer**

- Place old and new clients on different server processes; commit one change and verify both authorized clients display it once.
- Disconnect during a write, reconnect and verify the UI converges to the persisted state; unauthorized origins and subscriptions must fail.

- https://guides.rubyonrails.org/action_cable_overview.html
- https://turbo.hotwired.dev/handbook/streams

### Caching and shared state

**Inspect the source:** List reconstructible values separately from sessions, counters and locks. Capture key namespaces, serialized values, TTLs and invalidation triggers; Rails.cache and request-local SQL query caching have different lifetimes.

**During coexistence:** Recommendation: use a new cache namespace when representations differ, read through to the authoritative data and maintain invalidation for both deployed readers. Keep shared coordination uses on their qualified store while moving only disposable values.

**Target design:** A deliberately selected Rails cache store; Solid Cache is database-backed and new Rails 8 applications include it. MemoryStore and RedisCacheStore remain choices with different process/service boundaries.

**Rollback:** Recommendation: switch to the previous namespace/store and repopulate from domain data; retain the old backend while any coordination capability still depends on it.

**Benefits and prerequisites**

- Database-backed caching can remove a dedicated cache service when it is the last use. Requires: Reconstructible contents, tested expiration/invalidation, adequate database capacity and retirement of all other service uses. Tradeoffs: Cache traffic moves to the database; cache durability does not make it the authoritative domain store.

**Checks before transfer**

- Warm both versions, update the source record and verify neither version serves the prior value after its specified invalidation boundary.
- Restart the application or empty only the dedicated cache namespace; requests must rebuild correctly without losing identity or coordination state.

- https://guides.rubyonrails.org/caching_with_rails.html

### Delivery and recovery

**Inspect the source:** Record deployed web and Cable processes, configured workers/dispatchers, recurring.yml tasks and database connections. A web deployment alone does not establish that workers or broadcasts are running.

**During coexistence:** Recommendation: give each process a named operational owner; keep existing backups and queue monitoring while deploying the new cohort. Transfer recurring schedule ownership once, after checking overlapping old and new schedules.

**Target design:** An explicit Rails process inventory with worker lifecycle, queue failure inspection and the chosen realtime adapter. Recommendation: document separate domain, queue and cache database restore ordering where applicable.

**Rollback:** Recommendation: freeze producers and scheduler ownership transfers, restore the previous process configuration, then reconcile work accepted by the new deployment.

**Benefits and prerequisites**

- Recommendation: visible process and schedule ownership makes failures and recovery boundaries reviewable. Requires: A deployment recipe for the selected adapters and an exercised restore/restart procedure. Tradeoffs: Coexistence temporarily operates both systems; one healthy HTTP endpoint cannot certify all processes.

**Checks before transfer**

- Stop a worker, enqueue a representative task and verify monitoring identifies its growing age; restart and observe the intended completion/failure policy.
- Restore a disposable backup and prove domain records, delayed tasks and session behavior are accounted for before promoting traffic.

- https://guides.rubyonrails.org/active_job_basics.html
- https://guides.rubyonrails.org/action_cable_overview.html

Rewrite workflow: /rewrite/guide.md
Shared contracts and phases: /rewrite/contracts.json


## Setup
1. Start in a source directory in a local development workspace with supported Ruby/Rails, PostgreSQL and Git installed; choose and record compatible versions before generation.
2. Run rails new product -d postgresql; enter product; configure development/test database access without committing credentials.
3. Run bin/rails generate authentication; add Pundit via bundle add pundit and run bin/rails generate pundit:install; implement signup and resource policies deliberately.
4. Run bin/rails db:prepare; start bin/rails server. Configure Solid Queue from the installed-version guide and run its worker with bin/jobs when enabled.
5. Add bundler-audit to the development group if absent; preserve Gemfile.lock. Define authorization, duplicate-job and migration tests.
6. Prepare config/deploy.yml, secrets, database persistence and backup/restore procedure. Only in an authorized deployment run bundle exec kamal setup initially or bundle exec kamal deploy thereafter.

## Verification
Commands below require the selected tools and project configuration. Replace example paths, schemes and IDs; distinguish tests from builds and interactive startup. These are instructions, not recorded passes.

```text
ruby --version
bundle exec rails --version
bin/rails db:migrate:status
bin/rails zeitwerk:check
bin/rails test
bin/rails test:system
bin/rubocop
bin/brakeman
bundle exec bundler-audit check --update
```

## Open questions
- Which signup, SSO/MFA and resource authorization policies are required?
- Does the product fit server-owned screens or need substantial offline/client state?
- Which host, mail/storage services, worker throughput and restore requirements apply?

## Sources
- [Hotwire: HTML Over The Wire](https://hotwired.dev/)
- [Active Record Migrations](https://guides.rubyonrails.org/active_record_migrations.html)
- [Getting Started with Rails](https://guides.rubyonrails.org/getting_started.html)
- [Pundit README](https://github.com/varvet/pundit)
- [Devise README](https://github.com/heartcombo/devise)
- [Active Job Basics](https://guides.rubyonrails.org/active_job_basics.html)
- [Testing Applications](https://guides.rubyonrails.org/testing.html)
- [Securing Rails Applications](https://guides.rubyonrails.org/security.html)
- [bundler-audit README and license](https://github.com/rubysec/bundler-audit)
- [Kamal Installation](https://kamal-deploy.org/docs/installation/)
- [Rails and AI](https://rubyonrails.org/ai)
- [Rails MIT License](https://github.com/rails/rails/blob/main/MIT-LICENSE)
- [Rails Action Mailer](https://guides.rubyonrails.org/action_mailer_basics.html)
- [Rails Active Storage](https://guides.rubyonrails.org/active_storage_overview.html)
- [Rails Action Cable](https://guides.rubyonrails.org/action_cable_overview.html)

Keep the project README current and link the selected guidance; preserve existing project instructions.
