routing について
`config/routes.rb`で簡潔なRESTfulルートを設計するRails開発者向けスキルです。リソースのネスト、名前空間、制約、APIルート、ルートテストを扱います。
主な機能
- 01必要なRESTfulリソースルートのみ生成
- 02浅いネストによるリソース整理
- 03`namespace`と`scope`の使い分け
- 04メンバールートとコレクションルートの定義
- 05制約とAPIルートの設定
- 06ルートのテストとデバッグ
活用シーン
- 01コントローラーに必要なルートだけ公開
- 02親の文脈を保ちつつ子の個別URLを簡潔にする
- 03管理領域やAPIバージョンを名前空間で整理
- 04ルート対応を確認して競合を調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ thinkoodle/ rails-skills --skill routingインストール後、Claude Code で/routingを入力して読み込まれたか確認します。
同梱ファイルreference.md
詳細
Rails ルーティングのエキスパート
すっきりとした、RESTful で最小限のルートを定義します。余分なルートは不要な攻撃対象領域を増やし、routes.rb を読むほかの開発者を混乱させます。
基本方針
- デフォルトは RESTful —
resourcesとresourceを使います。カスタムルートは設計上の問題を示す兆候です。 - 最小限の範囲にする — 必要なものだけを公開するため、必ず
only:またはexcept:を使います。 - パスヘルパーをどこでも使う — URL をハードコードすると、ルートの変更時に壊れます。
_pathと_urlヘルパーを使います。 - ネストは1階層まで — リソースを1階層より深くネストしないでください。深いネストは扱いにくい URL やヘルパー名を生みます。
- カスタムアクションより新しいリソース — 多くのカスタムアクションを追加するなら、新しいコントローラーが必要です。
このスキルを使う場面
config/routes.rbのルートを追加または変更するとき- namespace、scope、module のどれを使うか判断するとき
- リソースをネストするとき
- member または collection ルートを追加するとき
- API のバージョニングを設定するとき
- ルートの競合をデバッグするとき
- ルートをテストするとき
手順
ステップ1:既存のルートを確認する
新しいルートを追加する前に、現在のルートを確認してください — 重複や競合は、見つけにくいバグの原因になります。
# Show all routes
bin/rails routes
# Filter by controller
bin/rails routes -c users
# Search by path or helper name
bin/rails routes -g admin
# Find unused routes (clean these up!)
bin/rails routes --unused
ステップ2:リソースを正しく使う
resources(複数形)— コレクション向け
index、show、new、create、edit、update、destroy の7つのルートを生成します。
# Generates all 7 routes — unused routes create dead endpoints and confuse devs
resources :articles
# Better — only generate what the controller actually implements
resources :articles, only: [:index, :show, :create]
このコントローラーが実際に実装しているアクションはどれか、確認してください。 そのアクションだけをルーティングします。
resource(単数形)— シングルトン向け
ユーザーまたはコンテキストごとに1つしか存在しない場合に使います。index アクションはありません。URL に :id は含まれません。
# Current user's profile — there's only one
resource :profile, only: [:show, :edit, :update]
# Current user's session
resource :session, only: [:new, :create, :destroy]
# App-wide dashboard
resource :dashboard, only: :show
⚠️ 重要:resource(単数形)でも、ルーティング先のコントローラーは複数形です。
resource :profile → ProfilesController(ProfileController ではありません)
パスヘルパーは単数形です:profile_path(profiles_path ではありません)、edit_profile_path。
単数形と複数形の使い分け:
- ユーザーが複数を所有または閲覧できますか? →
resources(複数形) - そのコンテキストには1つだけありますか? →
resource(単数形) indexアクションが適切ですか? →resources(複数形)
ステップ3:リソースをネストする(最大1階層)
子リソースが親の中でのみ意味を持つ場合にネストします。
# GOOD — 1 level deep
resources :articles do
resources :comments, only: [:index, :create, :destroy]
end
# Avoid — 2+ levels creates unwieldy URLs and painful helper names
resources :users do
resources :articles do
resources :comments # ← /users/1/articles/2/comments/3
end
end
子リソースが独自の識別性を持つ場合は、浅いネストを使います。
# GOOD — collection routes nested, member routes flat
resources :articles do
resources :comments, shallow: true
end
# Creates:
# /articles/:article_id/comments (index, create, new)
# /comments/:id (show, edit, update, destroy)
作成や一覧表示では親のコンテキストを利用でき、直接アクセスにはすっきりした URL を使えます。
ネストされたパスヘルパーには親が含まれます:
article_comments_path(@article) # GET /articles/1/comments
article_comment_path(@article, @comment) # GET /articles/1/comments/5
# With shallow:
comment_path(@comment) # GET /comments/5
ステップ4:namespace、scope、module の違い
この3つは似ていますが、動作は異なります。取り違えると、わかりにくいルーティングバグの原因になります。
namespace — すべてを変更する(パス+モジュール+ヘルパー)
namespace :admin do
resources :articles, only: [:index, :show]
end
- パス:
/admin/articles - コントローラー:
Admin::ArticlesController(app/controllers/admin/内) - ヘルパー:
admin_articles_path
用途: 管理パネル、API バージョン、独自のコントローラーを持つ個別のセクション。
scope module: — モジュールのみを変更する(パスは変更しない)
scope module: :admin do
resources :articles, only: [:index, :show]
end
- パス:
/articles(プレフィックスなし!) - コントローラー:
Admin::ArticlesController - ヘルパー:
articles_path
用途: URL を変更せずに、コントローラーをサブディレクトリに整理するとき。
scope path: — パスのみを変更する(モジュールは変更しない)
scope "/admin" do
resources :articles, only: [:index, :show]
end
- パス:
/admin/articles - コントローラー:
ArticlesController(モジュールなし!) - ヘルパー:
articles_path
用途: 個別のコントローラーネームスペースを設けずに、URL にプレフィックスを付けるとき。
クイックリファレンス
| メソッド | URL プレフィックス | コントローラーモジュール | ヘルパープレフィックス |
|---|---|---|---|
namespace :admin | /admin | Admin:: | admin_ |
scope module: :admin | なし | Admin:: | なし |
scope "/admin" | /admin | なし | なし |
迷ったら、namespace を使ってください。 最も明示的で、予想外の動作が少ない方法です。
ステップ5:Member ルートと Collection ルート
標準の RESTful アクション7つ以外のルートが必要な場合に使います。
Member ルート — 特定のレコード1件に対する操作(:id が必要)
resources :articles, only: [:index, :show] do
member do
patch :publish # PATCH /articles/:id/publish
patch :archive # PATCH /articles/:id/archive
end
end
生成されるルート:publish_article_path(@article)、archive_article_path(@article)
Collection ルート — コレクションに対する操作(:id は不要)
resources :articles, only: [:index, :show] do
collection do
get :search # GET /articles/search
get :drafts # GET /articles/drafts
delete :clear_all # DELETE /articles/clear_all
end
end
生成されるルート:search_articles_path、drafts_articles_path
省略形(単一ルート)
resources :photos do
get :preview, on: :member # GET /photos/:id/preview
get :search, on: :collection # GET /photos/search
end
⚠️ STOP — 本当にカスタムアクションが必要ですか?
カスタムアクションを2つ以上追加する場合は、新しいリソースが必要な可能性があります。
# BAD — too many custom actions on one resource
resources :articles do
member do
patch :publish
patch :unpublish
patch :archive
patch :unarchive
patch :feature
end
end
# GOOD — extract a new resource
resources :articles, only: [:index, :show]
resources :article_publications, only: [:create, :destroy] # publish/unpublish
resources :article_archives, only: [:create, :destroy] # archive/unarchive
Step 6: ルートの懸念事項(DRY な共有パターン)
複数のリソースで同じネストされたルートを共有する場合:
concern :commentable do
resources :comments, only: [:index, :create, :destroy]
end
concern :taggable do
resources :tags, only: [:index, :create, :destroy]
end
resources :articles, concerns: [:commentable, :taggable]
resources :photos, concerns: [:commentable]
resources :videos, concerns: [:commentable, :taggable]
Step 7: 制約
セグメント制約(パラメーターを検証)
resources :users, constraints: { id: /\d+/ }
リクエスト制約(サブドメイン、形式など)
constraints subdomain: "api" do
namespace :api do
resources :articles, only: [:index, :show]
end
end
高度な制約(カスタムロジック)
# Lambda
get "*path", to: "errors#not_found",
constraints: lambda { |req| !req.path.start_with?("/admin") }
# Object (responds to matches?)
class ApiConstraint
def matches?(request)
request.headers["Accept"]&.include?("application/json")
end
end
constraints ApiConstraint.new do
resources :articles, only: [:index, :show]
end
Step 8: ルート
ルートは必ず定義してください:
root "pages#home"
名前空間付きのルート:
namespace :admin do
root "dashboard#show" # /admin → Admin::DashboardController#show
end
Step 9: リダイレクト
# Simple redirect (301 by default)
get "/old-path", to: redirect("/new-path")
# With status code
get "/old-path", to: redirect("/new-path", status: 302)
# Dynamic redirect
get "/articles/:id", to: redirect("/posts/%{id}")
Step 10: エンジンのマウント
mount Sidekiq::Web => "/sidekiq"
mount ActionCable.server => "/cable"
Step 11: API ルート
namespace :api do
namespace :v1 do
resources :articles, only: [:index, :show, :create, :update, :destroy]
resources :users, only: [:show]
end
end
API 専用コントローラーの場合は、defaults: { format: :json }を使用します:
namespace :api, defaults: { format: :json } do
namespace :v1 do
resources :articles, only: [:index, :show]
end
end
Step 12: direct と resolve(カスタム URL ヘルパー)
direct — カスタム URL ヘルパーを作成
direct :homepage do
"https://example.com"
end
# homepage_url => "https://example.com"
direct :cdn_image do |model|
"https://cdn.example.com/#{model.image_path}"
end
# cdn_image_url(@photo) => "https://cdn.example.com/photos/1.jpg"
resolve — モデルのポリモーフィックルーティングをカスタマイズ
form_withでresource(単数形)を使う場合に必要です:
resource :basket, only: [:show, :update]
resolve("Basket") { [:basket] }
# Now form_with(model: @basket) generates /basket (not /baskets/:id)
ルートファイルの構成
config/routes.rbは次のように並べます:
Rails.application.routes.draw do
# 1. Root
root "pages#home"
# 2. Authentication / sessions
resource :session, only: [:new, :create, :destroy]
resources :registrations, only: [:new, :create]
# 3. Core resources (most used first)
resources :articles do
resources :comments, only: [:index, :create], shallow: true
end
# 4. Namespaced sections
namespace :admin do
root "dashboard#show"
resources :users
end
namespace :api, defaults: { format: :json } do
namespace :v1 do
resources :articles, only: [:index, :show]
end
end
# 5. Utility routes
get "up", to: "rails/health#show", as: :rails_health_check
# 6. Catch-all (if needed — put LAST)
get "*path", to: "errors#not_found"
end
ルートのテスト
# test/routing/articles_routing_test.rb
class ArticlesRoutingTest < ActionDispatch::IntegrationTest
test "routes to articles#index" do
assert_routing "/articles", controller: "articles", action: "index"
end
test "routes to articles#show" do
assert_routing "/articles/1", controller: "articles", action: "show", id: "1"
end
test "generates correct path" do
assert_generates "/articles/1", controller: "articles", action: "show", id: "1"
end
test "recognizes route" do
assert_recognizes(
{ controller: "articles", action: "create" },
{ path: "/articles", method: :post }
)
end
end
アンチパターン
- 必要なルートが2つなのに、7つすべてを生成する — 必ず
only:またはexcept:を使います - 1階層より深くネストする — shallow または別のリソースを使ってフラット化します
- パスをハードコードする — ハードコードした文字列はルートの変更時に壊れます。
"/articles/#{@article.id}"ではなくarticle_path(@article)を使います match ... via: :allを使う — すべての HTTP 動詞を公開すると攻撃対象領域が広がるため、明示的に指定します- カスタムのメンバー/コレクションアクションが多すぎる — 代わりに新しいリソースを切り出します
- namespace/scope/moduleを混同する — Step 4の表を確認します
resourcesのつもりでresourceを使う — 単数形は1つのリソースを表し、indexも:idもありません- ネストされたリソースに
only:/except:を指定し忘れる — ネストされたルートはすぐに肥大化します - 単数形の
resourceでresolveを忘れる — 指定しないとform_withが動作しません - ルートの順序に関するバグ — より具体的なルートを一般的なルートより上に置きます(Railsは上から順に照合し、最初に一致したルートを使います)
デバッグ
# See what a specific URL maps to
bin/rails routes -g "GET /articles"
# Check for conflicts
bin/rails routes | grep articles
# In rails console
Rails.application.routes.url_helpers.articles_path
app.edit_article_path(Article.first)
詳細なパターン、例、エッジケースについては、このスキルディレクトリのreference.mdを参照してください。