Skip to content
Published on

モダン Ruby & Rails 2026 — Ruby 3.4 / Rails 8 / Hotwire / Sorbet / Kamal 2 / Solid Queue 徹底ガイド

Authors

1章 · 2026年の Ruby/Rails — 「死んだ」と言った人へ

2026年の風景。友人が YC Demo Day で自分のスタートアップを自慢している。「3週間で MVP を作って公開し、最初の50万ドル売上を達成した」。スタックを聞かれた彼は少し恥ずかしそうに答える。「Rails」。隣のテーブルの Next.js フルスタックエンジニアが目を丸くする。「まだ?」

まだだ。そして2026年には、これまで以上に静かに、これまで以上に多くの場所で動いている。

2020年代初頭のインターネット言説は「Rails は死んだ」だった。2026年の現実はその逆 — Rails は死んでいないし、むしろ最も静かに、最も多く働いているスタックの一つになっている。GitHub、Shopify、Stripe、Airbnb、Basecamp、HEY、GitLab、Square、Coinbase、クックパッド、メルカリ、カカオペイの一部、トスの一部。挙げていくとキリがない。

なぜ死んでいないか。三つのことが同時に起きた。

  1. Ruby 3.4(2024年12月) — YJIT(Yet Another JIT)が本当に速くなり、error_highlight がデフォルトになってデバッグがずっと親切になった。
  2. Rails 8(2024年11月) — 37signals が Redis 依存を切り離す「Solid 三兄弟」(Queue, Cache, Cable)を発表した。Postgres または SQLite 一つでジョブキュー、キャッシュ、WebSocket をすべて回せる。
  3. Kamal 2kamal deploy 一行で自前のサーバーに Docker でデプロイする。Heroku/Vercel/AWS Fargate に月数百〜数千ドルを払わなくてもいい時代になった。

その上に Hotwire(Turbo + Stimulus)がある — React のビルドパイプラインなしでもインタラクティブな UI を作れる。Sorbet(Stripe 製の漸進的型チェッカー)と Tapioca(RBI 自動生成器)が型を入れてくれる。Mission Control が Sidekiq なしでジョブを監視する。Propshaft が sprockets を置き換えた新しいアセットパイプラインで、Action Notifier が Rails 8 の新しい通知フレームワークだ。

この記事は2026年の Ruby/Rails フルスタックを一気に見る。1章から11章までは各パーツの解剖、12章から14章までは代替と生態系と「誰が選ぶべきか」を見る。最後まで読めば、次のサイドプロジェクトで Next.js+Vercel の代わりに Rails 8+Kamal を選んだときに後悔しないかへの答えが出るはずだ。


2章 · Ruby 3.4(2024.12) — YJIT 改善と error_highlight デフォルト化

Ruby 3.4 は2024年12月25日(毎年クリスマスが Ruby のメジャーリリース日だ)に出た。大きなヘッドラインは二つ。

YJIT — Ruby の JIT は本物だ

YJIT(Yet Another JIT)は Shopify が作ったメソッドベースの JIT コンパイラで、Ruby 3.1 で実験的に入り、3.2 で本番投入可能になり、3.3 で ARM64 対応とメモリ使用量を減らし、3.4 でウォームアップ時間をさらに短縮した。Shopify 自身のベンチマークによれば Rails ワークロードで平均 1.4x ~ 2x 速くなる。単純な算術ループのようなマイクロベンチではもっと極端な数字が出るが、本当の意味は Rails コントローラーアクションのような実ワークロードで測られる。

有効化は一行。

# config/boot.rb または環境変数
# config/boot.rb の先頭
require 'bootsnap/setup' if ENV['DISABLE_BOOTSNAP'].nil?

# 環境変数で
RUBY_YJIT_ENABLE=1 bundle exec rails server

またはコードで明示的に。

# config/application.rb
require_relative "boot"
require "rails/all"

if defined?(RubyVM::YJIT.enable)
  RubyVM::YJIT.enable
end

# ...

Rails 8 は production 環境で YJIT をデフォルトで有効化する(config/environments/production.rb で明示的に切らない限り)。YJIT 統計を見るには。

# Rails console で
RubyVM::YJIT.runtime_stats
# => {compile_time_ns: ..., compiled_iseq_count: ..., ...}

error_highlight — デフォルトで親切になったエラーメッセージ

Ruby 3.1 で入った error_highlight gem が 3.4 でさらに洗練された。NoMethodError が起きたとき単に "undefined method foo'" ではなく、**ソースコードのどの部分で起きたかをキャレット(^`)で示してくれる**。

伝統的な Ruby 1.x ~ 2.x のエラーはこうだった。

NoMethodError: undefined method `name' for nil:NilClass
  app/models/user.rb:42:in `display'

3.4 ではこう。

app/models/user.rb:42:in 'User#display':
    puts "Hello, " + user.profile.name
                              ^^^^^
NoMethodError: undefined method 'name' for nil

user.profile が nil だということが一目で分かる。Sentry / Honeybadger / Bugsnag のようなエラートラッキングサービスもこのキャレット情報をそのまま保存する。

その他の変更

  • it ブロックパラメータが正式に入った — [1,2,3].map { it * 2 } のように単一パラメータブロックを短く書ける。
  • Range#stepRange を返していたのが Enumerator を返すように変わった。
  • Prism パーサーがデフォルトになった — Ruby 標準パーサーを置き換える新パーサーで、IDE/LSP ツールがずっと速くなる。
  • ガベージコレクタのチューニング可能なオプションが増えた(MMTk 互換モード)。

3章 · Rails 8(2024.11) — Solid トリオで Redis 依存を切り離す

Rails 8 は2024年11月に出た。DHH のキーノートの一言がすべてを要約する。

「We're going Redis-free by default.」

数年間、Rails アプリの標準インフラはこうだった: Postgres + Redis + Sidekiq。Redis は Sidekiq ジョブキュー、Rails キャッシュ、Action Cable パブサブに同時に使われていた。Redis が死ねばジョブも死に、キャッシュも死に、WebSocket も死ぬ。小さなチームが Redis を運用するのは常にコストだった。

Rails 8 の答え: Solid Queue, Solid Cache, Solid Cable。三つともデータベース(Postgres/MySQL/SQLite)にデータを書き込む。Redis が消えると依存ツリーが大きく単純化する。

[従来の Rails 7]                   [Rails 8 デフォルト]
─────────────────                  ─────────────
Rails app                          Rails app
   |                                  |
   ├── Postgres (データ)              └── Postgres
   ├── Redis (Sidekiq キュー)              ├── solid_queue_* テーブル
   ├── Redis (キャッシュ)                   ├── solid_cache_entries テーブル
   └── Redis (Action Cable)                └── solid_cable_messages テーブル

Rails 8 の新規アプリはデフォルトで SQLite で Solid 三つを全部回すこともできる — 本当にシングルバイナリに近い単純さだ。小さなサイドプロジェクトなら EC2 t4g.small 一台に SQLite + Solid Queue + Solid Cache + Solid Cable + Kamal で十分。

Rails 8 新規アプリ作成。

gem install rails -v "~> 8.0"
rails new myapp
# デフォルトで SQLite + Solid Queue + Solid Cache + Solid Cable + Propshaft + Importmap + Hotwire

Postgres を使うなら。

rails new myapp --database=postgresql

既存の Rails 7 アプリを 8 に上げる方法は別途ガイドが必要なくらいなのでここでは触れない — Rails Edge Guides の Upgrade Guide を参考にしてほしい。


4章 · Solid Queue — Redis 依存除去の意味

Sidekiq は10年以上 Rails ジョブキューの事実上の標準だった。Redis をバックエンドに使い、BRPOP でジョブを取得し、fork+threads モデルでワーカーを動かす。速くて堅牢だ。だが欠点は? Redis が必要だ。

Solid Queue は37signals が HEY と Basecamp で実際に使っていたジョブキューをオープンソース化したもの。Postgres/MySQL/SQLite にジョブテーブルを作り、FOR UPDATE SKIP LOCKED(または SQLite のトランザクション)でジョブを掴む。

インストールは簡単。

bundle add solid_queue
bin/rails solid_queue:install
bin/rails db:migrate

config/queue.yml でワーカーを設定する。

default: &default
  dispatchers:
    - polling_interval: 1
      batch_size: 500
  workers:
    - queues: "*"
      threads: 5
      processes: 1
      polling_interval: 0.1

development:
  <<: *default

production:
  <<: *default
  workers:
    - queues: [ critical, default ]
      threads: 10
      processes: 2
      polling_interval: 0.1
    - queues: [ low_priority ]
      threads: 3
      processes: 1
      polling_interval: 1

ジョブクラスは ActiveJob の標準インターフェースをそのまま使う。

class WelcomeEmailJob < ApplicationJob
  queue_as :default

  def perform(user_id)
    user = User.find(user_id)
    UserMailer.welcome(user).deliver_now
  end
end

# どこからでも
WelcomeEmailJob.perform_later(user.id)
WelcomeEmailJob.set(wait: 1.hour).perform_later(user.id)

Sidekiq から Solid Queue に移行するときコード変更はほぼない(ActiveJob インターフェースを使っていれば)。ただし 性能特性が異なる — Redis インメモリキューより Postgres ディスクキューは100倍遅いが、小さなアプリには十分。分単位で数十万件処理するメガワークロードならまだ Sidekiq + Redis が良い。だが 分単位で数千件未満 なら Solid Queue が運用の単純さで勝つ。


5章 · Solid Cache + Solid Cable

Solid Cache

37signals の HEY は毎日テラバイト級のキャッシュを使う。Redis インメモリでは RAM コストが爆発した。答え? ディスクベースのキャッシュ。SSD が速くなった2020年代では、ディスクキャッシュも十分速い。

# config/cache.yml
production:
  database: cache
  store_options:
    max_age: <%= 2.weeks.to_i %>
    max_size: 256.megabytes
    namespace: <%= Rails.env %>

使い方は通常の Rails.cache と同じ。

Rails.cache.fetch("expensive_query", expires_in: 1.hour) do
  ExpensiveQuery.compute
end

HEY 基準ではキャッシュヒットレイテンシが平均 1-3ms 程度。Redis より遅いが、価格対容量がはるかに大きい。

Solid Cable

Action Cable が Redis パブサブに依存していた部分をデータベースに移したもの。メッセージを solid_cable_messages テーブルに書き、LISTEN/NOTIFY(Postgres)またはポーリング(SQLite/MySQL)で購読者に配信する。

# config/cable.yml
production:
  adapter: solid_cable
  connects_to:
    database:
      writing: cable
  polling_interval: 0.1.seconds
  message_retention: 1.day

チャットメッセージを送るコントローラー。

class ChatMessagesController < ApplicationController
  def create
    message = @room.messages.create!(body: params[:body], user: current_user)

    # Solid Cable で他のユーザーにブロードキャスト
    ChatChannel.broadcast_to(@room, render_to_string(partial: "messages/message", locals: { message: message }))

    head :no_content
  end
end

数百同時接続のチャットなら十分。数万人以上 なら Redis アダプタ(AnyCable のような)や専用ソリューションが必要だ。


6章 · Hotwire(Turbo + Stimulus) — React なしのインタラクティブ

Hotwire は「HTML Over the Wire」の略。哲学はシンプル: JSON でデータを送ってクライアントが React でレンダリングする代わりに、サーバーが HTML 断片を送ってクライアントが DOM に差し込む

三つのパーツでできている。

  1. Turbo Drive — すべてのリンククリックとフォーム送信を fetch で奪い取り、応答 HTML の <body> だけを差し替える。SPA のようなページ遷移を無料で提供。
  2. Turbo Frames<turbo-frame id="cart"> のようなコンテナでページの一部だけを更新する。
  3. Turbo Streams — サーバーが「この要素を追加/置換/削除しろ」という命令を HTML 断片と一緒に送る。WebSocket や SSE でリアルタイム更新が可能。

Turbo Drive 例

<!-- app/views/posts/index.html.erb -->
<%= link_to "新しい投稿", new_post_path %>

このリンクをクリックすると Turbo が fetch で奪い取り、応答の <body> だけを差し替える。JavaScript を一行も書かずに SPA 感覚。

Turbo Frames 例

<!-- app/views/posts/show.html.erb -->
<h1><%= @post.title %></h1>

<turbo-frame id="comments">
  <%= render @post.comments %>
  <%= link_to "コメントを追加", new_post_comment_path(@post) %>
</turbo-frame>
<!-- app/views/comments/new.html.erb -->
<turbo-frame id="comments">
  <%= form_with model: [@post, @comment] do |f| %>
    <%= f.text_area :body %>
    <%= f.submit "投稿" %>
  <% end %>
</turbo-frame>

「コメントを追加」リンクをクリックすると id="comments" フレームの中だけでフォームがロードされる。ページ全体は変わらない。

Turbo Streams でリアルタイム更新

# app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :post
  belongs_to :user

  broadcasts_to ->(comment) { [comment.post, "comments"] }
end
<!-- app/views/posts/show.html.erb -->
<%= turbo_stream_from @post, "comments" %>

<div id="comments">
  <%= render @post.comments %>
</div>

これである誰かがコメントを追加すると、同じページを見ている全ユーザーに WebSocket(Solid Cable)で新しいコメントが即座に追加される。React/Vue 一行も書かずに。

Stimulus — 軽量な JS コントローラー

本当に複雑なクライアント相互作用が必要なときだけ Stimulus を使う。

<div data-controller="counter">
  <button data-action="click->counter#increment">+</button>
  <span data-counter-target="display">0</span>
</div>
// app/javascript/controllers/counter_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["display"]

  initialize() { this.count = 0 }

  increment() {
    this.count += 1
    this.displayTarget.textContent = this.count
  }
}

HTML が常に真実の源だ。React の「仮想 DOM が真実で実 DOM はそこから派生」とは正反対の哲学。


7章 · Sorbet(Stripe) + Tapioca — 漸進的型付け

Ruby は動的言語だ。変数に型がない。これは長所であり短所でもある。Stripe はコードベースが数百万行に成長して動的型付けの限界を痛感し、Sorbet を作った(2019年オープンソース化)。

Sorbet は Ruby に漸進的型付けを乗せる。すべてのファイルに一行追加する。

# typed: true
require "sorbet-runtime"

class User
  extend T::Sig

  sig { params(name: String, age: Integer).returns(User) }
  def self.create(name:, age:)
    new(name: name, age: age)
  end

  sig { params(name: String, age: Integer).void }
  def initialize(name:, age:)
    @name = name
    @age = age
  end

  sig { returns(String) }
  def display
    "#{@name} (#{@age})"
  end
end

# typed: true は strict 度合いを意味する(ignore / false / true / strict / strong)。sig { ... } でメソッドシグネチャを宣言する。実行時には sorbet-runtime が引数の型を検証してエラーを投げ、静的解析では srb tc で型チェックを回す。

bundle add sorbet
bundle add sorbet-runtime
bundle exec srb init
bundle exec srb tc

Tapioca — Sorbet の自動 RBI 生成器

Sorbet は .rbi ファイル(RBI = Ruby Interface)から型シグネチャを読む。外部 gem に対するシグネチャを直接書くのは地獄 — Tapioca が自動生成する。

bundle add tapioca --group=development
bundle exec tapioca init
bundle exec tapioca gems    # 全 gem の RBI 生成
bundle exec tapioca dsl     # Rails DSL(スコープ、アソシエーション、アトリビュート)の RBI 生成

Tapioca DSL が作った RBI の例。

# sorbet/rbi/dsl/user.rbi(自動生成)
class User
  sig { returns(T.nilable(String)) }
  def email; end

  sig { params(value: T.nilable(String)).returns(T.nilable(String)) }
  def email=(value); end

  # ActiveRecord スコープ
  sig { returns(ActiveRecord::Relation) }
  def self.active; end
end

これで IDE で user.emil のようなタイポも捕まる。Stripe 社内ではコードの80%以上が # typed: true 以上だという。


8章 · Kamal 2(37signals) — kamal deploy で直接デプロイ

37signals の DHH は2023年に「We have left the cloud」という記事で有名になった。Basecamp/HEY を AWS から自前データセンターに移して年700万ドルを節約したという記事だ。その移行過程で作ったツールが Kamal(元の名前は MRSK)。

Kamal 2(2024年リリース)は Docker + SSH + 一行のコマンド で任意の Linux サーバーにアプリをデプロイする。Heroku/Vercel/AWS Fargate なしで。

gem install kamal
kamal init

config/deploy.yml でデプロイを定義する。

service: myapp

image: myteam/myapp

servers:
  web:
    hosts:
      - 1.2.3.4
      - 1.2.3.5
  job:
    hosts:
      - 1.2.3.6
    cmd: bin/jobs

registry:
  server: ghcr.io
  username: myteam
  password:
    - KAMAL_REGISTRY_PASSWORD

env:
  clear:
    RAILS_ENV: production
    RAILS_LOG_TO_STDOUT: 1
  secret:
    - RAILS_MASTER_KEY
    - DATABASE_URL

accessories:
  postgres:
    image: postgres:16
    hosts:
      - 1.2.3.4
    env:
      secret:
        - POSTGRES_PASSWORD
    volumes:
      - /var/lib/postgresql/data:/var/lib/postgresql/data

proxy:
  ssl: true
  host: myapp.com

デプロイは一行。

kamal deploy

何をしているか。

  1. ローカルで Docker イメージビルド(または GitHub Actions でビルドしたものを pull)。
  2. レジストリに push。
  3. 全サーバーに SSH で入り、新しいイメージを pull。
  4. 新しいコンテナを起動し、Kamal Proxy(Traefik 後継)がトラフィックを差し替える無停止デプロイ。
  5. 古いコンテナをクリーンアップ。

初回デプロイ時は kamal setup を呼ぶと Docker インストールから全部やってくれる。

kamal setup       # Docker インストール + 初期デプロイ
kamal deploy      # 以降のデプロイ毎回
kamal rollback    # ロールバック
kamal logs        # ログ閲覧
kamal app exec --interactive --reuse "bin/rails console"  # console 接続

EC2 t4g.medium 一台(月30ドル)に Postgres + Solid Queue + Rails アプリを全部立てれば、Heroku hobby tier(月25ドル)に近い価格ではるかに強力なインスタンス になる。トラフィックが増えればサーバーを増やして kamal deploy --hosts ... で水平スケール。


9章 · Mission Control — Rails ネイティブのジョブ監視

Sidekiq には常に良い Web UI(/sidekiq)があった。Solid Queue に移ったらこれを失うのか? いや — Rails 8 は Mission Control - Jobs という公式 Web UI を提供する。

bundle add mission_control-jobs
# config/routes.rb
Rails.application.routes.draw do
  mount MissionControl::Jobs::Engine, at: "/jobs"
  # ...
end
# config/application.rb
config.mission_control.jobs.base_controller_class = "AdminController"  # 認証

/jobs に接続するとキュー状態、進行中ジョブ、失敗ジョブ、リトライ、ジョブ一時停止/再開がすべてできる。Sidekiq Web UI のほぼ全機能を提供しつつ、Solid Queue の全アダプタ(Postgres/MySQL/SQLite)で動作する。

加えてジョブ監視に良いツール。

  • Skylight / Scout APM — ジョブのメモリ/CPU プロファイリング。
  • Sentry / Honeybadger — ジョブ失敗時のエラー追跡とアラート。
  • Datadog — Solid Queue テーブルメトリクスを直接収集。

10章 · Propshaft / Action Notifier — Rails 8 の新しい基本

Propshaft — sprockets の後継

Sprockets は Rails 2 からあったアセットパイプラインだ。CoffeeScript、Sass、ERB で JavaScript と CSS をコンパイルし、ダイジェストフィンガープリントを付けてキャッシュする。だが2020年代には esbuild、Vite、Bun、importmap-rails のようなツールがトランスパイルをもっとうまくやる。Propshaft はトランスパイルしない — ファイルにダイジェストフィンガープリントだけ付けて配信する。

# Gemfile
gem "propshaft"
# ダイジェストが付いたファイルを生成
bin/rails assets:precompile

新しいアセットパイプライン哲学。

  • トランスパイルは esbuild/Vite/jsbundling-rails の仕事。
  • バンドリングもそれらの仕事。
  • Propshaft は単純にダイジェストフィンガープリントと配信だけ。

Rails 8 の新規アプリはデフォルトで Propshaft + Importmap-rails + Hotwire を使う。JavaScript は importmap でブラウザがネイティブに取得し、CSS は propshaft がフィンガープリントする。

Action Notifier(Rails 8 新規追加)

Rails 8 には通知(notification)システムが正式に入った。メール、SMS、プッシュ、Slack、データベースなど複数チャネルに同時に通知を送る標準抽象化だ。

class NewCommentNotifier < ApplicationNotifier
  deliver_by :email, mailer: "CommentMailer", method: "new_comment"
  deliver_by :slack, channel: "#engineering"
  deliver_by :database

  param :comment, :user

  def message
    "#{params[:user].name}が新しいコメントを投稿しました。"
  end
end

# 呼び出し
NewCommentNotifier.with(comment: @comment, user: current_user).deliver(@post.author)

複数チャネルに同じメッセージを送る一般的なパターンを一箇所にまとめる。Noticed gem が着想元で、Rails 8 で公式化された。


11章 · RuboCop / Standard / Brakeman — 静的解析

RuboCop — 最も使われているリンター

RuboCop は Ruby の ESLint 的な存在。スタイル違反から潜在バグまで捕まえる。

bundle add rubocop --group=development
bundle exec rubocop
bundle exec rubocop -a   # 自動修正

rubocop-railsrubocop-rspecrubocop-performance のような拡張 gem で範囲を広げる。

# .rubocop.yml
require:
  - rubocop-rails
  - rubocop-rspec
  - rubocop-performance

AllCops:
  TargetRubyVersion: 3.4
  TargetRailsVersion: 8.0
  NewCops: enable

Style/StringLiterals:
  EnforcedStyle: double_quotes

Layout/LineLength:
  Max: 120

Standard — 意見のない RuboCop ラッパー

毎回 .rubocop.yml を決めるのが疲れるなら Standard を使おう。RuboCop をラップしつつ「もう全部決めたから黙ってろ」という哲学。

bundle add standard --group=development
bundle exec standardrb
bundle exec standardrb --fix

Tenderlove(Aaron Patterson)や多くの OSS メンテナが使っている。Prettier が JS でやったことを Ruby でやる。

Brakeman — 静的セキュリティ解析

Brakeman は Rails 専用の静的セキュリティ解析ツール。SQL インジェクション、mass assignment、XSS、コマンドインジェクションのような一般的な脆弱性を捕まえる。

bundle add brakeman --group=development
bundle exec brakeman

CI パイプラインに入れて PR ごとに回すと良い。GitHub Actions の例。

name: Brakeman
on: [pull_request]
jobs:
  brakeman:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: 3.4
          bundler-cache: true
      - run: bundle exec brakeman --no-pager

Rails 8 の新規アプリはデフォルトで RuboCop、Brakeman、そして GitHub Actions ワークフローを一緒に作ってくれる。


12章 · 代替 — Hanami 2 / Roda / Sinatra / dry-rb

Rails が全ての答えではない。Ruby 生態系には他に良い選択肢がある。

Hanami 2

Hanami は「Rails よりも明示的でモジュール化されたフレームワーク」を目指す。2022年に Hanami 2 が出てから本格的にプロダクション利用できるレベルになった。特徴。

  • 依存性注入(DI) コンテナが第一級市民。dry-system の上に作られている。
  • ルーター / コントローラー(アクション) / ビュー / ビューモデルが明示的に分離。
  • 「慣習より明示的」哲学。マジックが少ない。
# slices/main/actions/books/index.rb
module Main
  module Actions
    module Books
      class Index < Main::Action
        include Deps[
          "repositories.book_repo"
        ]

        def handle(_request, response)
          response[:books] = book_repo.all
        end
      end
    end
  end
end

小〜中規模の API/SaaS に適している。Rails より学習曲線は急だが、大きなコードベースで明示的な構造が価値を持つときに輝く。

Roda

Jeremy Evans(Sequel 作者)が作ったマイクロルーティングツリーベースのフレームワーク。非常に高速で、ツリールーティングでルートが自然にネストする。

require "roda"

class App < Roda
  plugin :json
  plugin :halt

  route do |r|
    r.root { { hello: "world" } }

    r.on "users" do
      r.get Integer do |id|
        { user_id: id }
      end

      r.post do
        # 新規ユーザー
      end
    end
  end
end

Sinatra より速く、Hanami より軽量。Shrine、Sequel のようなライブラリと相性が良い。

Sinatra

最も古いマイクロフレームワーク。シンプルな API や小さなツールには今でも良い。

require "sinatra"

get "/hello/:name" do
  "Hello, #{params[:name]}"
end

dry-rb / Rom-rb

dry-rb は Hanami が基盤としているライブラリコレクション。dry-validation(スキーマ検証)、dry-monads(Result/Maybe モナド)、dry-types(型)、dry-system(DI コンテナ)、dry-effects(エフェクト)。Rails と一緒にも使える。

Rom-rb はデータマッパーパターンの ORM。ActiveRecord と違ってドメインオブジェクトと永続性を分離する。DDD を真面目にやるチームに魅力的。


13章 · 韓国 / 日本の Ruby 生態系 — クックパッド、メルカリ、カカオペイ

日本 — Ruby の本場

Ruby は Matz(まつもとゆきひろ)が1995年に作った。日本は Ruby の本場で、日本企業の Ruby/Rails 利用が最も深く長い。

  • クックパッド — 日本最大のレシピサイト。ほぼ全てのバックエンドが Rails。RubyKaigi のメインスポンサー。Ruby/Rails カンファレンスの発表で社内モノリスをどう運用しているかをよく共有している。
  • メルカリ — 日本最大のフリマアプリ。バックエンドの大部分が Ruby/Rails で、他は Go/Python に移行中。Go に移す部分も Ruby で始まったビジネスロジックを少しずつマイグレーションする。
  • GMO ペパボ — ライブコマース/EC。
  • マネーフォワード — 家計簿/会計 SaaS。Rails で始まり成長したケース。
  • Sansan — 名刺管理。Rails ベース。
  • freee — 会計 SaaS。Rails で IPO まで到達した。

日本では毎年 RubyKaigi(2-3日のカンファレンス)が開かれ、Matz が直接キーノートをする。Rails より Ruby 自体に関する発表が多く、MRI/YJIT/言語デザインの深い議論が日常だ。

韓国 — Rails 利用は増えている

韓国の Rails 利用は日本ほど大きくはないが、着実に増えている。

  • カカオペイ — 一部のバックエンドが Rails。決済/メンバーシップ関連の一部ドメイン。
  • トス — 一部の社内ツールと社内システムが Rails。
  • Bridge Plus / D.CAMP — スタートアップアクセラレータがよく Rails で開始するスタートアップを見る。
  • よく知られた韓国の Rails 事例 — Class101、Mirinae、Zigbang の一部バックオフィスなど。

韓国は2010年代中盤に RubyKR カンファレンスが活発で、最近は RubyKR Slack/Discord コミュニティが小さいながら活発。韓国での Rails 開発者採用は日本ほどではないが着実にある(特にシニア/スタートアップテックリードポジションで)。

グローバル — よく知られた事例

  • Shopify — 世界最大の Rails アプリ。YJIT の主要スポンサー。Rails コアへの多数のコントリビューション。
  • GitHub — Rails で始まり今も大部分が Rails。モノリス運用の模範例。
  • Stripe — Sorbet の発祥地。決済処理バックエンドの大部分が Ruby。
  • Airbnb — 初期から Rails。一部は Java/Kotlin に移行、一部はまだ Ruby。
  • Basecamp / HEY — 37signals 自社プロダクト。DHH のビジョンがそのまま具現化された場所。
  • GitLab — Rails。セルフホストと SaaS 両方。
  • Twitch — 初期 Rails、一部は Go/Erlang に移行。
  • Coinbase — Rails で開始、一部は Go に移行。

14章 · 誰が Ruby/Rails を選ぶべきか — 1-2人 / スタートアップ / B2B SaaS

1-2人 / サイドプロジェクト

圧倒的に Rails を推奨する。 理由。

  • 速度rails new 一行で認証、ジョブキュー、キャッシュ、WebSocket、マイグレーション、テスト、管理画面までセットアップされる。
  • インフラの単純さ — Rails 8 + Solid トリオ + SQLite で EC2 t4g.small 一台に全部立てられる。
  • Kamal でデプロイコストがほぼ0 — Heroku/Vercel なしで月5ドルから始められる。
  • Hotwire でフロントエンドビルドパイプラインなし — Node 依存ゼロ。

Next.js + Vercel + Postgres + Upstash Redis + Resend + Stripe で組むより、Rails + Kamal + Postgres 一つで組む方が速くて安い。

初期スタートアップ(1-15人)

Rails はまだ最良の選択。 37signals、GitHub、Stripe、Shopify が全部 Rails で始まり IPO/ユニコーンまで到達した。PMF を探す段階ではフルスタックフレームワークの凝集力が絶対的だ。Next.js のモノレポ + RPC + 認証ライブラリ + ジョブキュー依存ツリーは PMF 段階では荷物。

良い組み合わせ。

  • Rails 8 + Postgres + Solid トリオ + Hotwire + Kamal 2。
  • Sentry でエラー追跡、Skylight または Scout APM で APM。
  • RSpec または Minitest、FactoryBot、Capybara/Cuprite でテスト。
  • 漸進的に Sorbet 導入(最初から全部 strict にせず、コアドメインから)。

B2B SaaS(15-100人)

依然 Rails が良い。ただし 型とドメイン分離 にもっと気を配ろう。

  • Sorbet でコアドメインの型を整える。
  • Service Object / Command パターンで fat controller を避ける。
  • Trailblazer、dry-rb、Hanami のパターンを部分的に導入。
  • モノリスを維持しつつ Engine でドメインを分離(Shopify の「Modular Monolith」パターン)。

100人以上 / マイクロサービス段階

Rails もよく動くが、この時点では言語選択より 組織設計 がもっと重要になる。Rails モノリスが自然に分解されるなら、一部のサービスを Go/Java/Kotlin に移すことを検討する価値がある(Shopify がコアは Rails、一部のインフラ系サービスを Go/Rust に移行したように)。

Rails を避けるべき場合

  • 超低レイテンシ(<10ms)がビジネスの中核 な場合 — ゲームサーバー、高頻度取引、組み込み。Go、Rust、Elixir、C++ が良い。
  • 機械学習推論/学習パイプラインが本業 の場合 — Python が答え。
  • 型が絶対的に必須 なドメイン — Sorbet である程度解決するが、最初から TypeScript/Kotlin/Rust の方が直線的な経路。
  • 既に Node/Python 単一言語ポリシー の会社 — わざわざ Ruby を追加する理由がない。

15章 · 参考 / References