Ruby: The Language That Still Ships
Ruby has been declared dead so many times that the community stopped counting. Node.js was going to kill it. Go was going to kill it. Python’s explosion in ML was going to kill it. The shift to microservices was going to kill it.
Shopify still runs the largest Rails app in the world. GitHub deploys a two-million-line Rails monolith twenty times a day. Stripe powers global fintech on Ruby. Basecamp is still building product on the framework it created. The language that was supposed to die keeps shipping.
What’s less well understood is that Ruby and Rails have changed substantially. YJIT—a JIT compiler written in Rust—has made Ruby 3.4 roughly twice as fast as Ruby 2.7 on real workloads. Rails 8 ships with a deployment tool, a job queue, a cache, and a WebSocket layer that require no Redis, no managed queues, and no cloud vendor. The economics of 2026—where profitable, lean SaaS companies matter more than hypergrowth unicorns—happen to favor exactly what Ruby and Rails have always been good at.
Who Still Uses Ruby in 2026
The companies running Ruby in production are not startups trying to ship fast and hoping to rewrite later. They’re mature enterprises who’ve evaluated the alternatives and stayed:
Shopify hosts millions of merchants, processes trillions in GMV annually, and describes itself as “the biggest Rails app in the world.” Their engineering team wrote YJIT.
GitHub runs a Rails monolith with over 2 million lines of code, 1,000+ engineers contributing to it, deployed 20 times per day. They’ve talked publicly about the cost of rewrites and chose to keep Rails.
Stripe powers global payment infrastructure with Ruby. Their public API client libraries and internal services run on it.
Basecamp created Rails and continues to build Basecamp and HEY on it. DHH and the 37signals team are actively pushing Rails forward with Rails 8 and Kamal.
The pattern: these are all profitable, engineering-led companies with the resources to switch if they wanted to. They haven’t because Ruby and Rails remain genuinely productive.
Performance: YJIT Changed the Story
Ruby’s reputation for slowness was earned. MRI Ruby (the reference implementation) has a Global VM Lock that prevents true parallelism, and historically its raw execution speed lagged behind compiled languages and JVM-based alternatives.
YJIT (Yet Another JIT) changed the baseline. It’s an in-process JIT compiler written in Rust, integrated into MRI Ruby starting in 3.1 and production-ready since 3.2. The Shopify team wrote it specifically to address their latency and throughput needs.
On Shopify’s production benchmark suite (x86-64), YJIT 3.4 is approximately 92% faster than the Ruby interpreter. On typical web application workloads—the kind Rails apps actually run—expect 40-70% throughput improvements over Ruby 2.7 with no code changes.
|
|
YJIT 3.4 improvements over 3.3:
- Better method inlining (both Ruby and C methods)
- Megamorphic call site optimization
- 5-7% faster than 3.3.6 on the same benchmarks
- Slightly lower memory usage despite compiling more code
Ruby is not Go or Java in raw throughput. But for web applications that spend most of their time waiting on database queries and network I/O, raw throughput is rarely the binding constraint. The question is whether Ruby is fast enough—and for most web workloads, it is.
Concurrency: Fibers, Ractors, and the GVL
Ruby’s concurrency story has three distinct layers with different maturity levels.
Fibers and the Fiber::Scheduler
Fibers are lightweight coroutines—cooperative green threads that yield control explicitly. The Fiber::Scheduler interface (Ruby 3.0+) allows blocking I/O operations to be transparently redirected to an event loop:
|
|
The Falcon web server uses this model. Where Puma uses OS threads (each costing ~1MB of stack), Falcon uses fibers (~4KB each). A single process can handle thousands of concurrent connections. The constraint: libraries must be fiber-aware (non-blocking). The async gem ecosystem has matured to cover most common operations.
For new applications where raw concurrency matters, Falcon + async is worth evaluating. For existing applications on Puma, the threading model works fine—virtual threads aren’t Ruby’s answer yet, but Fibers cover the async I/O case effectively.
Threads
Ruby threads exist and work, but the GVL means only one thread executes Ruby code at a time. They’re useful for I/O-bound concurrency (multiple database queries in parallel) but don’t provide CPU parallelism.
|
|
Ractors (Experimental)
Ractors provide true parallelism by giving each Ractor its own GVL. They’re the answer to “can Ruby use multiple CPU cores?”
|
|
The catch: Ractors enforce strict object isolation. Objects must be explicitly sent between Ractors via message passing. Mutable shared state causes errors. Most existing Ruby gems are not Ractor-safe.
Ractors remain experimental in Ruby 3.4. They’re genuinely useful for CPU-intensive background jobs where you control the data flow, but they’re not a drop-in concurrency solution for general application code. Think of them as a tool for specific high-CPU tasks, not a replacement for the threading model.
Optional Type Safety with RBS
Ruby is dynamically typed, but Ruby 3+ ships with RBS—a type signature language for describing Ruby programs. Type signatures live in .rbs files, leaving source code unchanged:
|
|
# sig/user.rbs — separate type signatures
class User
attr_accessor email: String
attr_accessor name: String
attr_reader created_at: Time
def self.create: (email: String, password: String) -> User
def authenticate: (password: String) -> bool
end
Run type checking with Steep:
|
|
The main trade-off versus Sorbet (the other popular Ruby type checker):
| RBS + Steep | Sorbet | |
|---|---|---|
| Type location | Separate .rbs files |
Inline annotations in source |
| Source code impact | None | Requires # typed: true headers and T. annotations |
| Adoption | Gradual, file-by-file | More invasive |
| Ecosystem | Bundled with Ruby | Larger existing gem support |
| Style | Like TypeScript’s .d.ts |
Like Python type hints |
For new projects: RBS + Steep is the official path. For projects that need deep type coverage quickly and have the team buy-in to annotate source: Sorbet. Both are production-ready in 2026—the “experimental” label is gone.
Neither matches TypeScript’s breadth of ecosystem signatures, but coverage improves every year as gem maintainers add RBS signatures.
Rails 8: The “No PaaS Required” Release
Rails 8 (November 2024) is built around a specific philosophy: you should be able to run a serious production application on a single server with no cloud vendors and no managed services. It ships the tooling to make that real.
The Solid Stack
Rails 8 replaces three common Redis dependencies with database-backed alternatives:
Solid Queue replaces background job queues (Sidekiq, Resque):
|
|
Solid Queue uses FOR UPDATE SKIP LOCKED—a SQL feature in PostgreSQL, MySQL 8+, and SQLite—to efficiently poll for jobs without distributed locking. No Redis, no Sidekiq process, just your database.
Solid Cache replaces Redis-backed caching:
|
|
For most applications, the database is already on fast local storage. Solid Cache can be faster than Redis when Redis is a network hop away.
Solid Cable replaces Redis-backed Action Cable (WebSockets):
|
|
Real-time features via Turbo Streams now work without Redis. The polling interval is configurable based on your latency requirements.
Authentication Generator
|
|
Generates a complete authentication system:
Usermodel withhas_secure_passwordSessionmodel tracking IP, user agent, and expirationSessionsControllerfor sign-in/sign-outPasswordResetsControllerfor email-based password recovery- Before-action helpers for requiring authentication
|
|
No Devise dependency for the common case. Roll in gems for OAuth, two-factor, and other extensions as needed.
SQLite in Production
Rails 8 defaults to SQLite for new applications and makes the case that SQLite is appropriate for production:
|
|
What makes this work:
- WAL mode enabled automatically (Write-Ahead Logging allows concurrent readers with one writer)
- Litestream for continuous replication to S3 (point-in-time recovery)
- Single-server deployment required (SQLite is file-based, not network-accessible)
- Surprising performance: reads from SQLite on local NVMe are often faster than PostgreSQL over a network
The constraint is real: once you need multiple application servers, you need PostgreSQL or MySQL. But for a single-server deployment handling thousands of daily requests, SQLite eliminates the database server entirely.
Kamal: Deployment Without Kubernetes
Kamal (formerly MRSK) is the deployment tool built by 37signals and included in Rails 8 by default. It takes a bare Linux server and deploys your Docker containers with a single command:
|
|
|
|
Kamal Proxy (the bundled reverse proxy, replacing Traefik) handles TLS via Let’s Encrypt, health checks, and rolling restarts with zero downtime.
The complete stack for a new Rails 8 app:
- Hetzner or Linode VPS (~$20/month for 4 vCPU / 8GB)
- Kamal for deployment
- Solid Queue + Solid Cache + Solid Cable (all database-backed)
- SQLite with Litestream for persistence
- Kamal Proxy for TLS and routing
No Heroku, no AWS, no Redis, no managed databases. This is a legitimate production stack for applications handling thousands of daily users.
Hotwire: Interactive UIs Without React
Hotwire (Turbo + Stimulus) is Rails’ answer to the JavaScript framework problem. The premise: most interactivity in web applications can be achieved by streaming HTML fragments from the server rather than building a JavaScript SPA.
Turbo Drive
Intercepts link clicks and form submissions, replaces page content without a full reload. Zero configuration—it’s on by default.
Turbo Frames
Scope a section of the page for independent navigation and form submission:
|
|
The controller returns only the frame content on subsequent requests—no full page reload, no JavaScript written.
Turbo Streams
Broadcast real-time HTML updates over WebSockets:
|
|
|
|
New comments appear instantly for all viewers, no polling, no JavaScript event handlers.
Stimulus
Minimal JavaScript behavior bound via HTML attributes:
|
|
|
|
No component lifecycle, no state management, no build step complexity. Stimulus adds behavior to HTML rather than generating it.
The honest trade-off: Hotwire excels for CRUD applications, dashboards, and content-heavy sites. For highly interactive UIs with complex client-side state (draggable kanban boards, collaborative editors, complex form wizards), React or Vue may still be warranted. But a surprising amount of “SPA-required” functionality is actually achievable with Turbo Frames and a little Stimulus.
Modern Ruby Patterns
Pattern Matching
Pattern matching (stable since Ruby 3.1) provides expressive data destructuring:
|
|
Pattern matching integrates naturally with Rails because API responses and database records are typically hashes. The in form raises NoMatchingPatternError if nothing matches; use case/in for exhaustive matching.
Endless Methods
|
|
Data Class
Data.define (Ruby 3.2+) creates immutable value objects—similar to records but without the full record semantics:
|
|
Tooling
Version Management
rbenv remains reliable and widely used. For new setups, mise manages Ruby alongside Node.js, Python, and other runtimes from a single .tool-versions file:
|
|
|
|
Testing
RSpec remains the dominant testing framework for Rails applications:
|
|
Minitest ships with Rails and is preferred in some codebases for its simplicity and speed.
RuboCop
The universal Ruby linter. Most Rails projects enforce it in CI:
|
|
|
|
When to Choose Ruby and Rails
Ruby and Rails are genuinely excellent for:
Rapid product development. Rails conventions make common patterns (authentication, admin, file uploads, email, background jobs, caching) fast to implement. A small team can ship substantial product in weeks.
CRUD-heavy applications. Blogs, admin panels, SaaS dashboards, e-commerce—the domain Rails was designed for. The Active Record pattern, form helpers, and view layer are optimized for this.
Early-stage SaaS. The combination of fast iteration and Rails 8’s single-server story (SQLite + Solid Stack + Kamal) means you can launch on $20/month and grow without infrastructure rewrites.
Small, full-stack teams. One Rails developer can own the entire stack—frontend (Hotwire), backend, database migrations, background jobs, deployment. The framework absorbs infrastructure decisions.
Where Ruby struggles:
CPU-intensive workloads. Image processing, video transcoding, heavy numerical computation—offload these to Rust, Go, or dedicated services. Ruby is not the right tool here.
Massive scale requiring horizontal distribution. Shopify runs Rails at enormous scale, but they have dedicated infrastructure teams and custom Rails internals. For most applications, you’ll hit database limits before Ruby limits, but architectural decisions matter at scale.
Teams requiring static typing as a foundation. RBS + Steep works, but it’s not TypeScript or Kotlin. If your team’s confidence comes from comprehensive type coverage, you’ll need to invest in the RBS ecosystem or consider a more typed language.
Latency-sensitive real-time systems. Sub-millisecond processing, high-frequency trading, game servers—use a compiled language.
The architecture that works well in 2026: Rails for the web tier and domain logic, PostgreSQL for persistence, Sidekiq or Solid Queue for background work, Go or Rust as escape hatches for genuinely CPU-bound tasks. This is not exotic—it’s what mature Rails shops actually run.
The Rails 8 Default Stack at a Glance
|
|
|
|
Rails hasn’t changed what it is. It’s still opinionated, convention-over-configuration, and optimized for developer productivity. What’s changed is the surrounding ecosystem: better performance (YJIT), a real deployment story (Kamal), and infrastructure consolidation (Solid Stack) that reduces operational complexity for the applications it’s designed to build.
The language that was supposed to die is shipping.
Comments