mcpField
R
routing

routing

github.com/thinkoodle開発7191 回インストール8 週間前に更新
リポジトリ

routing について

`config/routes.rb`で簡潔なRESTfulルートを設計するRails開発者向けスキルです。リソースのネスト、名前空間、制約、APIルート、ルートテストを扱います。

主な機能

  1. 01必要なRESTfulリソースルートのみ生成
  2. 02浅いネストによるリソース整理
  3. 03`namespace`と`scope`の使い分け
  4. 04メンバールートとコレクションルートの定義
  5. 05制約とAPIルートの設定
  6. 06ルートのテストとデバッグ

活用シーン

  1. 01コントローラーに必要なルートだけ公開
  2. 02親の文脈を保ちつつ子の個別URLを簡潔にする
  3. 03管理領域やAPIバージョンを名前空間で整理
  4. 04ルート対応を確認して競合を調査

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/thinkoodle/rails-skills --skill routing

インストール後、Claude Code で/routingを入力して読み込まれたか確認します。

同梱ファイルreference.md

詳細

Rails ルーティングのエキスパート

すっきりとした、RESTful で最小限のルートを定義します。余分なルートは不要な攻撃対象領域を増やし、routes.rb を読むほかの開発者を混乱させます。

基本方針

  1. デフォルトは RESTful — resources と resource を使います。カスタムルートは設計上の問題を示す兆候です。
  2. 最小限の範囲にする — 必要なものだけを公開するため、必ず only: または except: を使います。
  3. パスヘルパーをどこでも使う — URL をハードコードすると、ルートの変更時に壊れます。_path と _url ヘルパーを使います。
  4. ネストは1階層まで — リソースを1階層より深くネストしないでください。深いネストは扱いにくい URL やヘルパー名を生みます。
  5. カスタムアクションより新しいリソース — 多くのカスタムアクションを追加するなら、新しいコントローラーが必要です。

このスキルを使う場面

  • 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/adminAdmin::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

アンチパターン

  1. 必要なルートが2つなのに、7つすべてを生成する — 必ずonly:またはexcept:を使います
  2. 1階層より深くネストする — shallow または別のリソースを使ってフラット化します
  3. パスをハードコードする — ハードコードした文字列はルートの変更時に壊れます。"/articles/#{@article.id}"ではなくarticle_path(@article)を使います
  4. match ... via: :allを使う — すべての HTTP 動詞を公開すると攻撃対象領域が広がるため、明示的に指定します
  5. カスタムのメンバー/コレクションアクションが多すぎる — 代わりに新しいリソースを切り出します
  6. namespace/scope/moduleを混同する — Step 4の表を確認します
  7. resourcesのつもりでresourceを使う — 単数形は1つのリソースを表し、indexも:idもありません
  8. ネストされたリソースにonly:/except:を指定し忘れる — ネストされたルートはすぐに肥大化します
  9. 単数形のresourceでresolveを忘れる — 指定しないとform_withが動作しません
  10. ルートの順序に関するバグ — より具体的なルートを一般的なルートより上に置きます(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を参照してください。

コメント