Rails のルーティング

このガイドでは、開発者に向けてRailsのルーティング機能を解説します(訳注: routeとrootを区別するため、訳文ではrouteを基本的に「ルーティング」と訳します)。

このガイドの内容:

  • config/routes.rbのコードの読み方
  • 独自のルーティング作成法 (リソースベースのルーティングが推奨されますが、matchメソッドによるルーティングも可能です)
  • ルーティングのパラメータの宣言方法(コントローラのアクションに渡される)
  • ルーティングヘルパーを使ってパスやURLを自動生成する方法
  • 制限の作成やRackエンドポイントのマウントなどの高度な手法

目次

  1. Railsルーターの目的
  2. リソースベースのルーティング: Railsのデフォルト
  3. リソースフルでないルーティング
  4. リソースフルルーティングをカスタマイズする
  5. ルーティングを調べる
  6. ルーティングをテストする
  7. 巨大なルーティングファイルを分割する

1 Railsルーターの目的

Railsのルーター(router)は、URLパスに基づいて、受信したHTTPリクエストをRailsアプリケーション内の特定のコントローラーアクションに対応付けます(Rackアプリケーションに転送することも可能です)。ルーターは、ルーターで構成されたリソースに基づいて、パスとURLヘルパーも生成します。

1.1 受信したURLを実際のコードにルーティングする

RailsアプリケーションがHTTPリクエストを受け取ると、このリクエストをコントローラーのアクション(メソッドとも呼ばれます)に対応付けるようルーターに要求します。たとえば、以下の受信リクエストを考えてみましょう。

GET /users/17

最初にマッチしたのが以下のルーティングだとします。

get "/users/:id", to: "user#show"

このリクエストはUsersControllerクラスのshowアクションに一致し、paramsハッシュには{ id: '17' }が含まれます。

to:オプションは、コントローラ名#アクション名形式の文字列が渡されることを前提としています。 to:オプションでアクション名を文字列で指定する代わりに、action:オプションでアクション名のシンボルを指定することも可能です。 さらにcontroller:オプションも使えば、以下のように#記号なしの文字列を指定することも可能です。

get "/users/:id", controller: "users", action: :show

Railsではルーティングを指定するときにスネークケースを使います。たとえばUserProfilesControllerのような複合語のコントローラを使う場合は、user_profiles#showのように指定します。

1.2 コードからパスやURLを生成する

ルーターは、アプリケーションのパスやURLヘルパーメソッドを自動的に生成します。これらのメソッドを使うことで、パスやURL文字列をハードコードすることを回避できます。

たとえば、以下のルーティングを定義することで、user_pathuser_urlというヘルパーメソッドを利用できます。

get "/users/:id", to: "users#show", as: "user"

as:オプションは、ルーティングのカスタム名を指定するときに使います。ここで指定した名前は、URLとパスヘルパーを生成するときに使われます。

そして、アプリケーションのコントローラに以下のコードがあるとします。

@user = User.find(params[:id])

上記に対応するビューは以下です。

<%= link_to 'User Record', user_path(@user) %>

すると、ルーターによって/patients/17というパスが生成されます。これを利用することでビューが改修しやすくなり、コードも読みやすくなります。このidはルーティングヘルパーで指定する必要がない点にご注目ください。

ルーターは、user_path(@user)から/users/17というパスを生成します。このuser_pathヘルパーを使えば、ビューにパスをハードコードする必要がなくなります。こうすることで、最終的にルーティングを別のURLに移動するときに、対応するビューのコードを更新する必要がなくなるので、便利です。

ルーターは、同様の目的を持つuser_urlも生成します。上述のuser_pathが生成するのは/users/17のような相対URLですが、user_urlは上記の例で言うとhttps://example.com/users/17のような絶対URLを生成する点が異なります。

1.3 Railsルーターを設定する

アプリケーションやエンジンのルーティングはconfig/routes.rbファイルの中に存在します。これは、典型的なRailsアプリケーションでのルーティングの配置場所です。

次のセクションでは、このファイルで使われるさまざまなルーティングヘルパーについて説明します。

Rails.application.routes.draw do
  resources :brands, only: [:index, :show] do
    resources :products, only: [:index, :show]
  end

  resource :basket, only: [:show, :update, :destroy]

  resolve("Basket") { route_for(:basket) }
end

これは通常のRubyソースファイルなので、ルーティング定義には、条件やループなど、Rubyのあらゆる機能を利用できます。

ルーティング定義をラップするRails.application.routes.draw do ... endブロックは、ルーターDSL(Domain Specific Language: ドメイン固有言語)のスコープを確定するのに不可欠なので、削除してはいけません。

routes.rbファイルで変数名を使う場合は、ルーターのDSLメソッドと名前が衝突しないように十分ご注意ください。

2 リソースベースのルーティング: Railsのデフォルト

リソースベースのルーティング(以下リソースルーティング)を使うことで、指定のリソースコントローラでよく使われるすべてのルーティングを手軽に宣言できます。resourcesを宣言するだけで、コントローラのindexshowneweditcreateupdatedestroyアクションを個別に宣言しなくても1行で宣言が完了します。

2.1 Web上のリソース

ブラウザはRailsに対してリクエストを送信する際に、特定のHTTP verb(GETPOSTPATCHPUTDELETEなど)を使って、URLに対するリクエストを作成します。上に述べたHTTP verbは、いずれもリソースに対して特定の操作の実行を指示するリクエストです。リソースルーティングでは、関連するリクエストを1つのコントローラ内のアクションに割り当てます。

Railsアプリケーションが以下のHTTPリクエストを受け取ったとします。

DELETE /photos/17

このリクエストは、特定のコントローラ内アクションにマッピングさせるようルーターに要求しています。最初にマッチしたのが以下のルーティングだとします。

resources :photos

RailsはこのリクエストをPhotosController内のdestroyアクションに割り当て、paramsハッシュに{ id: '17' }を含めます。

2.2 CRUD、verb、アクション

Railsのリソースフルルーティングでは、(GET、PUTなどの)各種HTTP verb(動詞、HTTPメソッドとも呼ばれます) と、コントローラ内アクションを指すURLが対応付けられます。1つのアクションは、データベース上での特定のCRUD(Create/Read/Update/Delete)操作に対応付けられるルールになっています。たとえば、以下のようなルーティングが1つあるとします。

resources :photos

上の記述により、アプリケーション内に以下の7つのルーティングが作成され、いずれもPhotosControllerに対応付けられます。

HTTP verb パス コントローラ#アクション 目的
GET /photos photos#index すべての写真の一覧を表示
GET /photos/new photos#new 写真を1つ作成するためのHTMLフォームを返す
POST /photos photos#create 写真を1つ作成する
GET /photos/:id photos#show 特定の写真を表示する
GET /photos/:id/edit photos#edit 写真編集用のHTMLフォームを1つ返す
PATCH/PUT /photos/:id photos#update 特定の写真を更新する
DELETE /photos/:id photos#destroy 特定の写真を削除する

Railsのルーターでは、サーバーへのリクエストをマッチさせる際にHTTP verbとURLを組み合わせる形で使っているため、4種類のURL(/photos/photos/new/photos/:id/photos/:id/edit)を7種類の異なるアクション(indexshownewcreateeditupdatedestroy)に割り当てています。たとえば、同じphotos/パスであっても、HTTP verbがGETのときはphotos#indexにマッチし、HTTP verbがPOSTのときはphotos#createにマッチします。

Railsのルーティングファイルroutes.rbでは、ルーティングを記載する順序が重要であり、「上からの記載順に」マッチします。たとえば、resources :photosというルーティングがget 'photos/poll'よりも上の行にあれば、resources行のshowアクションがget行の記述よりも優先されるので、get 'photos/poll'行のルーティングは有効になりません。get 'photos/poll'を最初にマッチさせるには、get 'photos/poll'行をresourcesよりも上 に移動する必要があります。

2.3 パスとURL用ヘルパー

リソースフルなルーティングを作成すると、アプリケーションのコントローラやビューで多くのヘルパーが利用できるようになります。

たとえば、resources :photosというルーティングをルーティングファイルに追加すると、コントローラやビューで以下の_pathヘルパーが使えるようになります。

_pathヘルパー 返すURL
photos_path /photos
new_photo_path /photos/new
edit_photo_path(:id) /photos/:id/edit
photo_path(:id) /photos/:id

上記の:idなどのパスヘルパーのパラメーターは、生成されたURLに渡されます。つまり、edit_photo_path(10)/photos/10/editを返します。

これらの_pathヘルパーに対応する_urlヘルパー(photos_urlなど)も生成されます。_urlヘルパーは、同じパスの前に「現在のホスト名」「ポート番号」「パスのプレフィックス」を追加して返します。

"_path"や"_url"の前に付けられるプレフィックスには、ルーティング名が使われます。これは、rails routesコマンド出力の"prefix"列を確認することで特定できます。詳しくは、後述の既存のルールを一覧表示するを参照してください。

2.4 複数のリソースを同時に定義する

リソースをいくつも定義しなければならない場合は、以下のような略記法で一度に定義することでタイプ量を節約できます。

resources :photos, :books, :videos

上の記法は、以下の記法のショートカットです。

resources :photos
resources :books
resources :videos

2.5 単数形リソース

場合によっては、ユーザーがリソースを1個しか持たないことが前提となることもあります(この場合、そのリソースのすべての値を一覧表示するindexアクションを用意する意味はありません)。このような場合は、複数形のresourcesの代わりに単数形のresourceを指定できます。

以下のリソースフルなルーティングは、アプリケーション内に6つのルーティングを作成して、それらすべてをGeocodersコントローラーに対応付けます。

resource :geocoder
resolve("Geocoder") { [:geocoder] }

上のresolve呼び出しは、Geocoderのインスタンスをレコード識別を介して単数形ルーティングに変換するために必要です。

Geocodersコントローラに割り当てられた以下の6つのルーティングを作成します。

HTTP verb パス コントローラ#アクション 目的
GET /geocoder/new geocoders#new geocoder作成用のHTMLフォームを返す
POST /geocoder geocoders#create geocoderを作成する
GET /geocoder geocoders#show 1つしかないgeocoderリソースを表示する
GET /geocoder/edit geocoders#edit geocoder編集用のHTMLフォームを返す
PATCH/PUT /geocoder geocoders#update 1つしかないgeocoderリソースを更新する
DELETE /geocoder geocoders#destroy geocoderリソースを削除する

単数形リソースは、複数形のコントローラに対応付けられます。たとえば、geocoderという単数形リソースは、GeocodersControllerという複数形の名前を持つコントローラに対応付けられます。

単数形のリソースフルなルーティングを使うと、以下のヘルパーメソッドが生成されます。

_pathヘルパー 返すURL
new_geocoder_path /geocoder/new
edit_geocoder_path /geocoder/edit
geocoder_path) /geocoder

複数形リソースの場合と同様に、末尾が_urlで終わる同じヘルパー名でも「現在のホスト名」「ポート番号」「パスのプレフィックス」が含まれます。

2.6 コントローラの名前空間とルーティング

大規模なアプリケーションでは、コントローラーを名前空間でグループ化して整理したい場合があります。たとえば、app/controllers/adminディレクトリ内にあるAdmin::名前空間の下に、複数のコントローラーがあるとします。以下のようにnamespaceブロックを使うと、このようなグループへルーティングできます。

namespace :admin do
  resources :articles
end

上のルーティングにより、articlesコントローラやcommentsコントローラへのルーティングが多数生成されます。たとえば、Admin::ArticlesController向けに作成されるルーティングは以下のとおりです。

HTTP verb パス コントローラ#アクション 名前付きルーティングヘルパー
GET /admin/articles admin/articles#index admin_articles_path
GET /admin/articles/new admin/articles#new new_admin_article_path
POST /admin/articles admin/articles#create admin_articles_path
GET /admin/articles/:id admin/articles#show admin_article_path(:id)
GET /admin/articles/:id/edit admin/articles#edit edit_admin_article_path(:id)
PATCH/PUT /admin/articles/:id admin/articles#update admin_article_path(:id)
DELETE /admin/articles/:id admin/articles#destroy admin_article_path(:id)

上記の例では、namespaceのデフォルトの規則に沿って、すべてのパスに/adminプレフィックスが追加されていることにご注目ください。

2.6.1 モジュールを利用する

例外的に、(/adminが前についていない)/articlesAdmin::ArticlesControllerにルーティングしたい場合は、以下のようにscopeブロックでモジュールを指定できます。

scope module: "admin" do
  resources :articles
end

上は以下のようにscopeを使わない書き方も可能です。

resources :articles, module: "admin"
2.6.2 スコープを領する

逆に、/admin/articlesを(Admin::モジュールのプレフィックスなしの)ArticlesControllerにルーティングしたい場合は、以下のようにscopeブロックでパスを指定できます。

scope "/admin" do
  resources :articles
end

上は以下のようにscopeを使わない書き方も可能です。

resources :articles, path: "/admin/articles"

いずれの場合も、名前付きルーティング(named route)は、scopeを使わなかった場合と同じになることにご注目ください。最後の例の場合は、以下のパスがArticlesControllerに割り当てられます。

上述の2つの書き方(パスに/adminをプレフィックスしない場合、モジュールにAdmin::をプレフィックスしない場合)は、どちらの場合も、名前付きルーティングヘルパーはscopeを使わなかった場合と同じになります。

最後のケースでは、ArticlesControllerに以下のパスが対応付けられます。

HTTP verb パス コントローラ#アクション 名前付きルーティングヘルパー
GET /admin/articles articles#index articles_path
GET /admin/articles/new articles#new new_article_path
POST /admin/articles articles#create articles_path
GET /admin/articles/:id articles#show article_path(:id)
GET /admin/articles/:id/edit articles#edit edit_article_path(:id)
PATCH/PUT /admin/articles/:id articles#update article_path(:id)
DELETE /admin/articles/:id articles#destroy article_path(:id)

namespaceブロックの内部で異なるコントローラ名前空間を使いたい場合、「get '/foo', to: '/foo#index'」のような絶対コントローラパスを指定することもできます。

2.7 ネストしたリソース

他のリソースの配下に論理的な子リソースを配置することはよくあります。たとえば、Railsアプリケーションに以下のモデルがあるとします。

class Magazine < ApplicationRecord
  has_many :ads
end

class Ad < ApplicationRecord
  belongs_to :magazine
end

ルーティングをネストする(入れ子にする)宣言を使うことで、この親子関係をルーティングで表せるようになります。

resources :magazines do
  resources :ads
end

上のルーティングによって、雑誌(magazines)へのルーティングに加えて、広告(ads)をAdsControllerにもルーティングできるようになりました。ネストしたadsリソースの全ルーティングは以下のようになります。

HTTP verb パス コントローラ#アクション 目的
GET /magazines/:magazine_id/ads ads#index ある雑誌1冊に含まれる広告をすべて表示する
GET /magazines/:magazine_id/ads/new ads#new ある1冊の雑誌用の広告を1つ作成するHTMLフォームを返す
POST /magazines/:magazine_id/ads ads#create ある1冊の雑誌用の広告を1つ作成する
GET /magazines/:magazine_id/ads/:id ads#show ある雑誌1冊に含まれる広告を1つ表示する
GET /magazines/:magazine_id/ads/:id/edit ads#edit ある雑誌1冊に含まれる広告1つを編集するHTMLフォームを返す
PATCH/PUT /magazines/:magazine_id/ads/:id ads#update ある雑誌1冊に含まれる広告を1つ更新する
DELETE /magazines/:magazine_id/ads/:id ads#destroy ある雑誌1冊に含まれる広告を1つ削除する

これによって、パスとURLについて通常のルーティングヘルパーも作成されます。ヘルパーはmagazine_ads_urledit_magazine_ad_pathのような名前になります。adsリソースはmagazinesの下にネストしているので、adのURLではmagazineを省略できません。これらのヘルパーは、最初のパラメータとしてMagazineモデルのインスタンスを1つ受け取ります(magazine_ads_url(@magazine, @ad))。

2.7.1 ネスティング回数の上限

次のように、ネストしたリソースの中で別のリソースをネストできます。

resources :publishers do
  resources :magazines do
    resources :photos
  end
end

たとえば上のルーティング例は、アプリケーションで以下のようなパスとして認識されます。

/publishers/1/magazines/2/photos/3

このURLに対応するルーティングヘルパーはpublisher_magazine_photo_urlとなります。このヘルパーを使うには、毎回3つの階層すべてでオブジェクトを指定する必要があります。このように、リソースのネストを深くすると複雑になりすぎてしまい、ルーティングをメンテナンスしにくくなります。

リソースのネスティングの深さは、経験則として1階層にとどめておくべきです。

2.7.2 ネストを浅くする

(上記で推奨したような)深いネストを回避する方法の1つとして、コレクションアクションの生成場所を親のスコープに移動するという方法があります。この方法を使うと、階層化されたという感覚を得ながら、メンバーアクションをネストしないようにできます。言い換えると、最小限の情報でリソースを一意に指定できるルーティングを作成するということです。

"メンバー"アクションとは、個別のリソースに適用され、アクションの対象となる特定のリソースを識別するためにIDを指定する必要のあるアクション(showeditなど)を指します。"コレクション"アクションとは、リソースのセット全体を操作するアクション(indexなど)を指します。

resources :articles do
  resources :comments, only: [:index, :new, :create]
end
resources :comments, only: [:show, :edit, :update, :destroy]

上のルーティングでは、:onlyオプションを用いることで、指定したルーティングだけを生成するようRailsに指示しています。 この方法は、ルーティングの記述を複雑にせずに済み、かつ深いネストを作らずに済むという絶妙なバランスを保っています。:shallowオプションを使うことで、上と同じ内容をさらに簡単に記述できます。

resources :articles do
  resources :comments, shallow: true
end

これによって生成されるルーティングは、最初の例と完全に同じです。親リソースで:shallowオプションを指定すると、すべてのネストしたリソースが浅くなります。

resources :articles, shallow: true do
  resources :comments
  resources :quotes
end

このarticlesリソースでは以下のルーティングが生成されます。

HTTP verb パス コントローラ#アクション 名前付きルーティングヘルパー
GET /articles/:article_id/comments(.:format) comments#index article_comments_path
POST /articles/:article_id/comments(.:format) comments#create article_comments_path
GET /articles/:article_id/comments/new(.:format) comments#new new_article_comment_path
GET /comments/:id/edit(.:format) comments#edit edit_comment_path
GET /comments/:id(.:format) comments#show comment_path
PATCH/PUT /comments/:id(.:format) comments#update comment_path
DELETE /comments/:id(.:format) comments#destroy comment_path
GET /articles/:article_id/quotes(.:format) quotes#index article_quotes_path
POST /articles/:article_id/quotes(.:format) quotes#create article_quotes_path
GET /articles/:article_id/quotes/new(.:format) quotes#new new_article_quote_path
GET /quotes/:id/edit(.:format) quotes#edit edit_quote_path
GET /quotes/:id(.:format) quotes#show quote_path
PATCH/PUT /quotes/:id(.:format) quotes#update quote_path
DELETE /quotes/:id(.:format) quotes#destroy quote_path
GET /articles(.:format) articles#index articles_path
POST /articles(.:format) articles#create articles_path
GET /articles/new(.:format) articles#new new_article_path
GET /articles/:id/edit(.:format) articles#edit edit_article_path
GET /articles/:id(.:format) articles#show article_path
PATCH/PUT /articles/:id(.:format) articles#update article_path
DELETE /articles/:id(.:format) articles#destroy article_path

ブロック内でshallowメソッドを使うと、すべてのネストが1階層浅くなるように内側にスコープを1つ作成します。これによって生成されるルーティングは、最初の例と完全に同じです。

shallow do
  resources :articles do
    resources :comments
    resources :quotes
  end
end

scopeメソッドには、「浅い」ルーティングをカスタマイズするためのオプションが2つあります。:shallow_path:shallow_prefixです。

:shallow_pathオプションは、指定されたパラメータをメンバーのパスの冒頭に追加します。

scope shallow_path: "sekret" do
  resources :articles do
    resources :comments, shallow: true
  end
end

上の場合、commentsリソースのルーティングは以下のようになります。

HTTP verb パス コントローラ#アクション 名前付きルーティングヘルパー
GET /articles/:article_id/comments(.:format) comments#index article_comments_path
POST /articles/:article_id/comments(.:format) comments#create article_comments_path
GET /articles/:article_id/comments/new(.:format) comments#new new_article_comment_path
GET /sekret/comments/:id/edit(.:format) comments#edit edit_comment_path
GET /sekret/comments/:id(.:format) comments#show comment_path
PATCH/PUT /sekret/comments/:id(.:format) comments#update comment_path
DELETE /sekret/comments/:id(.:format) comments#destroy comment_path

:shallow_prefixオプションを使うと、指定されたパラメータを_pathおよび_urlルーティングヘルパー名の冒頭に追加します。

scope shallow_prefix: "sekret" do
  resources :articles do
    resources :comments, shallow: true
  end
end

上の場合、commentsリソースのルーティングは以下のようになります。

HTTP verb パス コントローラ#アクション 名前付きルーティングヘルパー
GET /articles/:article_id/comments(.:format) comments#index article_comments_path
POST /articles/:article_id/comments(.:format) comments#create article_comments_path
GET /articles/:article_id/comments/new(.:format) comments#new new_article_comment_path
GET /comments/:id/edit(.:format) comments#edit edit_sekret_comment_path
GET /comments/:id(.:format) comments#show sekret_comment_path
PATCH/PUT /comments/:id(.:format) comments#update sekret_comment_path
DELETE /comments/:id(.:format) comments#destroy sekret_comment_path

2.8 ルーティングの「concern」機能

concernを使うことで、他のリソース内で使いまわせる共通のルーティングを宣言できます。concernは以下のようにconcernブロックで定義します。

concern :commentable do
  resources :comments
end

concern :image_attachable do
  resources :images, only: :index
end

concernを利用すると、同じようなルーティングを繰り返し記述せずに済み、複数のルーティング間で同じ振る舞いを共有できます。

resources :messages, concerns: :commentable

resources :articles, concerns: [:commentable, :image_attachable]

上のコードは以下と同等です。

resources :messages do
  resources :comments
end

resources :articles do
  resources :comments
  resources :images, only: :index
end

scopeブロック内やnamespaceブロック内では、以下のように複数形のconcernsを呼び出すことでも上と同じ結果を得られます。

namespace :messages do
  concerns :commentable
end

namespace :articles do
  concerns :commentable
  concerns :image_attachable
end

2.9 オブジェクトからパスとURLを作成する

ルーティングヘルパーを使う方法の他に、パラメータの配列からパスやURLを作成することもできます。例として、以下のようなルーティングがあるとします。

resources :magazines do
  resources :ads
end

magazine_ad_pathを使うと、idを数字で渡す代わりにMagazineAdのインスタンスを引数として渡せます。

<%= link_to 'Ad details', magazine_ad_path(@magazine, @ad) %>

上で生成されるパスは/magazines/5/ads/42のようになります。

以下のように複数のオブジェクトを持つ配列に対してurl_forを使うときも、上と同じようにパスを得られます。

<%= link_to 'Ad details', url_for([@magazine, @ad]) %>

上の場合、Railsは@magazineMagazineであり、@adAdであることを認識し、それに基づいてmagazine_ad_pathヘルパーを呼び出します。link_toヘルパーでは、url_for呼び出しを書かなくても、以下のようにずっと簡潔な方法でオブジェクトだけを指定できます。

<%= link_to 'Ad details', [@magazine, @ad] %>

1冊の雑誌にだけリンクしたい場合は、以下のように書きます。

<%= link_to 'Magazine details', @magazine %>

それ以外のアクションについては、配列の第1要素にアクション名を挿入する必要があります。たとえば、edit_magazine_ad_pathと書く代わりに以下のように書けます。

<%= link_to 'Edit Ad', [:edit, @magazine, @ad] %>

これにより、モデルのインスタンスをURLとして扱えるようになります。これはリソースフルなスタイルを採用する大きなメリットの1つです。

[@magazine, @ad]のようなオブジェクトからパスとURLを自動的に取得するために、RailsではActiveModel::NamingモジュールとActiveModel::Conversionモジュールのメソッドを利用してします。具体的には、@magazine.model_name.route_keymagazinesを返し、@magazine.to_paramはモデルのidの文字列表現を返します。したがって、[@magazine, @ad]オブジェクトに対して生成されるパスは、/magazines/1/ads/42のようになります。

2.10 RESTfulなルーティングをさらに追加する

デフォルトで作成されるRESTfulなルーティングは7つありますが、7つと決まっているわけではありません。必要であれば、コレクションやコレクションの各メンバーに対して適用されるルーティングを追加することも可能です。

以下のセクションでは、メンバールーティングとコレクションルーティングの追加について説明します。memberという用語は、単一の要素に作用するルーティング(showupdatedestroyなど)を指します。collectionという用語は、複数の要素(要素のコレクション)を操作するルーティング(indexルーティングなど)を指します。

2.10.1 メンバールーティングを追加する

memberブロックは、以下のようにリソースブロックに追加できます。

resources :photos do
  member do
    get "preview"
  end
end

上のルーティングはGETリクエストとそれに伴う/photos/1/previewを認識し、リクエストをPhotosコントローラのpreviewアクションにルーティングし、リソースid値をparams[:id]に渡します。同時に、preview_photo_urlヘルパーとpreview_photo_pathヘルパーも作成されます。

/photos/1/previewへの受信GETリクエストは、PhotosControllerpreviewアクションにルーティングされます。リソースID値はparams[:id]で得られます。また、preview_photo_urlヘルパーおよびpreview_photo_pathヘルパーも作成されます。

memberブロック内では、各ルート定義でHTTP verb(上記の例ではget 'preview'get)が指定されます。getの他に、patchputpost、またはdeleteも利用できます。

memberルーティングが1つしかない場合は、以下のようにルーティングで:onオプションを指定することでブロックを省略できます。

resources :photos do
  get "preview", on: :member
end

上の:onオプションは省略することも可能です。この場合、リソースidの値の取得にparams[:id]ではなくparams[:photo_id]を使う点を除いて、同じメンバールーティングが生成されます。ルーティングヘルパーも、preview_photo_urlphoto_preview_urlに、preview_photo_pathphoto_preview_pathにそれぞれリネームされます。

2.10.2 コレクションルーティングを追加する

ルーティングにコレクション(collection)を追加するには以下のようにcollectionブロックを使います。

resources :photos do
  collection do
    get "search"
  end
end

上のルーティングは、GETリクエスト + /photos/searchなどの(idを伴わない)パスを認識し、リクエストをPhotosコントローラのsearchアクションにルーティングします。このときsearch_photos_urlsearch_photos_pathルーティングヘルパーも同時に作成されます。

collectionルーティングでもmemberルーティングのときと同様に:onオプションを使えます。

resources :photos do
  get "search", on: :collection
end

追加のresourceルーティングをシンボルで第1引数として定義する場合は、文字列で定義した場合と振る舞いが同等ではなくなる点にご注意ください。文字列はパスとして推測されますが、シンボルはコントローラのアクションとして推測されます。

2.10.3 追加されたnewアクションへのルーティングを追加する

:onオプションを使って、たとえば以下のように別のnewアクションを追加できます。

resources :comments do
  get "preview", on: :new
end

上のようにすることで、GET + /comments/new/previewのようなパスが認識され、Commentsコントローラのpreviewアクションにルーティングされます。preview_new_comment_urlpreview_new_comment_pathルーティングヘルパーも同時に作成されます。

リソースフルなルーティングにアクションが多数追加されていることに気付いたら、それ以上アクションを追加するのをやめて、そこに別のリソースが隠されているのではないかと疑ってみる方がよいでしょう。

resourcesで生成されるデフォルトのルーティングやヘルパーは、カスタマイズ可能です。詳しくは、リソースフルルーティングをカスタマイズするセクションを参照してください。

3 リソースフルでないルーティング

Railsでは、resourcesによるリソースフルなルーティングの他に、任意のURLをアクションにルーティングすることも可能です。この方式を使う場合、リソースフルルーティングのような自動的なルーティンググループの生成は行われません。従って、アプリケーションで必要なルーティングを個別に設定することになります。

基本的にはリソースフルルーティングを使うべきですが、このような単純なルーティングの方が適している場合もあります。リソースフルルーティングが適していない場合に、アプリケーションのあらゆる部分を無理にリソースフルなフレームワークに押し込める必要はありません。

リソースフルでないルーティングが適しているユースケースの1つに、既存のレガシーURLを新しいRailsアクションに対応付ける場合があります。

3.1 パラメータの割り当て

通常のルーティングを設定する場合は、RailsがルーティングをブラウザからのHTTPリクエストに割り当てるためのシンボルをいくつか渡します。以下のルーティングを例にとってみましょう。

get "photos(/:id)", to: "photos#display"

/photos/1に対するブラウザからのGETリクエストが上のルーティングで処理されると、PhotosControllerdisplayアクションが呼び出され、URL末尾のパラメータ"1"へのアクセスはparams[:id]で行なえます。:idが必須パラメータではないことが丸かっこ()で示されているので、このルーティングは、上の例の/photosPhotosController#displayにルーティングすることもできます。

3.2 動的なセグメント

通常のルーティングの一部として、文字列を固定しない動的なセグメントを自由に使えます。あらゆるセグメントはparamsの一部に含めてアクションに渡せます。以下のルーティングを設定したとします。

get "photos/:id/:user_id", to: "photos#show"

上のルーティングは、/photos/1/2のようなパスにマッチします。このときアクションで使えるparams{ controller: "photos", action: "show", id: "1", user_id: "2" }となります。

デフォルトでは動的なセグメント分割にドット.を渡せません。ドットはフォーマット済みルーティングでは区切り文字として使われるためです。どうしても動的セグメント内でドットを使いたい場合は、デフォルト設定を上書きする制限を与えます。たとえばid: /[^\/]+/とすると、スラッシュ以外のすべての文字が使えます。

3.3 静的なセグメント

ルーティング作成時にコロンを付けなかった部分は、静的なセグメントとして固定文字列が指定されます。

get "photos/:id/with_user/:user_id", to: "photos#show"

上のルーティングは、/photos/1/with_user/2のようなパスにマッチします。このときアクションで使えるparams{ controller: 'photos', action: 'show', id: '1', user_id: '2' }となります。

3.4 クエリ文字列

クエリ文字列(訳注: URLの末尾に?パラメータ名=値の形式で追加されるパラメータ)で指定されているパラメータも、すべてparamsに含まれます。以下のルーティングを例にとってみましょう。

get "photos/:id", to: "photos#show"

ブラウザからのGETリクエストで/photos/1?user_id=2というパスが渡されると、通常通りPhotosControllerクラスのshowアクションに割り当てられます。このときのparams{ controller: 'photos', action: 'show', id: '1', user_id: '2' }となります。

3.5 デフォルトパラメータを定義する

:defaultsオプションにハッシュを1つ渡すことで、ルーティング内にデフォルト値を定義できます。このとき、動的なセグメントとして指定する必要のないパラメータを次のように適用することも可能です。

get "photos/:id", to: "photos#show", defaults: { format: "jpg" }

上のルーティングはブラウザからの/photos/12パスにマッチし、Photosコントローラのshowアクションに割り当てられます。params[:format]"jpg"に設定されます。

ブロック形式のdefaultsを使うと、複数の項目についてデフォルト値を設定することもできます。

defaults format: :json do
  resources :photos
  resources :articles
end

セキュリティ上の理由により、クエリパラメータでデフォルト値をオーバーライドすることはできません。オーバーライド可能なデフォルト値は、URLパスの置き換えによる動的なセグメントのみです。

3.6 名前付きルーティング

:asオプションを使うと、任意のルーティングの_pathヘルパーと_urlヘルパーで使われる名前を指定できます。

get 'exit', to: 'sessions#destroy', as: :logout

上のルーティングではlogout_pathlogout_urlがアプリケーションのルーティングヘルパーとして作成されます。logout_pathを呼び出すと/exitが返されます。

リソースを定義する前に、以下のようにasでカスタムルーティングを配置すると、リソースで定義されたルーティングヘルパー名をオーバーライドすることも可能です。

get ":username", to: "users#show", as: :user
resources :users

上のルーティングでは、/:username/janeなど)にマッチするuser_pathヘルパーが生成されます。UsersControllershowアクションの内部でparams[:username]にアクセスすると、ユーザー名を取り出せます。

3.7 HTTP verbを制限する

あるルーティングを特定のHTTP verbに割り当てるために、通常はgetpatchputpostdeleteメソッドのいずれかを使う必要があります。matchメソッドと:viaオプションを使うことで、複数のHTTP verbに同時にマッチするルーティングを作成できます。

match "photos", to: "photos#show", via: [:get, :post]

上のルーティングは、PhotosControllershowアクションに対してGETリクエストとPOSTリクエストの両方を受け取ります。

via: :allを指定すると、すべてのHTTP verbにマッチする特別なルーティングを作成できます。

match "photos", to: "photos#show", via: :all

1つのアクションにGETリクエストとPOSTリクエストの両方をルーティングすると、セキュリティ上の悪影響が生じます。たとえば、GETアクションはCSRFトークンをチェックしません(したがって、GETリクエストでデータベースに書き込むことは推奨されていません。詳しくはセキュリティガイドのCSRF対策を参照してください)。一般に、どうしても必要な理由がない限り、1つのアクションにすべてのHTTP verbをルーティングしてはいけません。

3.8 セグメントを制限する

:constraintsオプションを使って、動的セグメントのURLフォーマットを特定の形式に制限できます。

get "photos/:id", to: "photos#show", constraints: { id: /[A-Z]\d{5}/ }

上のルーティング定義では、idは5文字の英数字でなければなりません。したがって、上のルーティングは/photos/A12345のようなパスにはマッチしますが、/photos/893にはマッチしません。

以下のようにもっと簡潔な方法でも記述できます。

get "photos/:id", to: "photos#show", id: /[A-Z]\d{5}/

:constraintsでは正規表現を使えますが、ここでは正規表現の「アンカー(^$など)」は使えないという制限があることにご注意ください。たとえば、以下のルーティングは無効です。

get '/:id', to: 'articles#show', constraints: { id: /^\d/ }

対象となるすべてのルーティングは冒頭と末尾が既にアンカーされているので、このようなアンカーを指定する必要はないはずです。 以下の例をご覧ください。

get "/:id", to: "articles#show", constraints: { id: /\d.+/ }
get "/:username", to: "users#show"

上のルーティングでは、root名前空間が共有され、さらに以下の振る舞いも共有可能になります。

  • 常に数字で始まるルーティングパスの扱い(例: /1-hello-worldarticlesid値を渡す)
  • 数字で始まらないルーティングパスの扱い(例: /davidusersusername値を渡す)

3.9 リクエスト内容に応じて制限を加える

また、Stringを返すRequestオブジェクトの任意のメソッドに基いてルーティングを制限することもできます。

リクエストに応じた制限は、セグメントを制限するときと同様の方法で指定できます。

get "photos", to: "photos#index", constraints: { subdomain: "admin" }

上は、受信リクエストをadminサブドメインへのパスと照合します。

constraintsブロックで制限を指定することも可能です。

namespace :admin do
  constraints subdomain: "admin" do
    resources :photos
  end
end

上はhttps://admin.example.com/photosのようなURLにマッチします。

リクエストベースの制限は、Requestオブジェクトに対してあるメソッドを呼び出し、戻り値をハッシュと比較する形で機能します。たとえば、constraints: { subdomain: 'api' }という制限はapiサブドメインに期待どおりマッチしますが、constraints: { subdomain: :api }のようにシンボルを使った場合はapiサブドメインに一致しません(request.subdomainが返す'api'は文字列型であるため)。

制約の値は、対応するリクエストオブジェクトのメソッドの戻り値型と一致する必要があります。

formatの制限には例外があります。これはRequestオブジェクトのメソッドですが、すべてのパスに含まれる暗黙的なオプションのパラメータでもあります。formatの制限よりセグメント制限が優先され、format制約はハッシュを通じて強制される場合にのみ適用されます。たとえば、get "foo"、constraints: { format: "json" }GET /fooと一致します。

get "foo", constraints: lambda { |req| req.format == :json }のように制約でlambdaを指定すると、明示的なJSONリクエストへのルーティングのみを一致させることも可能です。

3.10 高度な制限

より高度な制限を使いたい場合、Railsで必要なmatches?に応答できるオブジェクトを渡す方法があります。例として、制限リストに記載されているすべてのユーザーをRestrictedListControllerにルーティングしたいとします。この場合、以下のように設定します。

class RestrictedListConstraint
  def initialize
    @ips = RestrictedList.retrieve_ips
  end

  def matches?(request)
    @ips.include?(request.remote_ip)
  end
end

Rails.application.routes.draw do
  get "*path", to: "restricted_list#index",
    constraints: RestrictedListConstraint.new
end

制限をlambdaとして指定することもできます。

Rails.application.routes.draw do
  get "*path", to: "restricted_list#index",
    constraints: lambda { |request| RestrictedList.retrieve_ips.include?(request.remote_ip) }
end

matches?メソッドおよびlambdaは、どちらも引数としてrequestオブジェクトを受け取ります。

3.10.1 制限をブロック形式で指定する

制限はブロック形式で指定することも可能です。これは以下のように、同一のルールを複数のルーティングに適用する必要がある場合に便利です。

class RestrictedListConstraint
  # ...上述の例と同じ
end

Rails.application.routes.draw do
  constraints(RestrictedListConstraint.new) do
    get "*path", to: "restricted_list#index"
    get "*other-path", to: "other_restricted_list#index"
  end
end

制限をlambdaで指定することもできます。

Rails.application.routes.draw do
  constraints(lambda { |request| RestrictedList.retrieve_ips.include?(request.remote_ip) }) do
    get "*path", to: "restricted_list#index"
    get "*other-path", to: "other_restricted_list#index"
  end
end

3.11 ワイルドカードセグメント

ルーティング定義ではワイルドカードセグメント(*)を利用できます。これは、*otherのように*をセグメントにプレフィックスしたものです。

get "photos/*other", to: "photos#unknown"

上のルーティングはphotos/12/photos/long/path/to/12long/path/toは長いパス)にマッチし、params[:other]には"12""long/path/to/12"が設定されます。冒頭にアスタリスク*が付いているセグメントを「ワイルドカードセグメント」と呼びます。

ワイルドカードセグメントを使うと、特定のパラメーター(上記の*other)がルーティングの残りの部分に一致する形で指定する、「ルートグロビング(route globbing)」と呼ばれる方法が利用できます。

したがって、上記のルーティングはphotos/12/photos/long/path/to/12に一致し、params[:other]には"12"が設定されます("long/path/to/12")。

ワイルドカードセグメントは、以下のようにルーティングのどの部分でも使えます。

get "books/*section/:title", to: "books#show"

上はbooks/some/section/last-words-a-memoirにマッチし、params[:section]には'some/section'が保存され、params[:title]には'last-words-a-memoir'が保存されます。

技術上は、1つのルーティングに2つ以上のワイルドカードセグメントを含めることは可能です。マッチャによるセグメントのパラメータ割り当ては、出現順に行われます。

get "*a/foo/*b", to: "test#index"

たとえば、上のルーティングはzoo/woo/foo/bar/bazにマッチし、params[:a]には'zoo/woo'が保存され、params[:b]には'bar/baz'が保存されます。

3.12 セグメントのフォーマット

以下のルーティング定義があるとします。

get "*pages", to: "pages#show"

このルーティングに対して'/foo/bar.json'をリクエストしたときのparams[:pages]は、"foo/bar"でリクエストフォーマットparams[:format]にJSONを指定したものと等しくなります。

formatのデフォルトの振る舞いは、URLにフォーマット指定が含まれていれば、それをURLから自動的にキャプチャしてparams[:format]に含めますが、この場合、URLのformatパラメータは必須ではありません。

フォーマットが明示的に指定されていない場合のURLにマッチさせ、フォーマット拡張子を含むURLを無視する場合は、以下のようにformat: falseオプションを指定できます。

get "*pages", to: "pages#show", format: false

逆に、フォーマットセグメントを必須にして省略不可にしたい場合は、format: trueを指定します。

get "*pages", to: "pages#show", format: true

3.13 リダイレクト

ルーティングでredirectを使うと、任意のパスを他のパスにリダイレクトできます。

get "/stories", to: redirect("/articles")

パスにマッチする動的セグメントを再利用してリダイレクトすることもできます。

get "/stories/:name", to: redirect("/articles/%{name}")

リダイレクトにブロックを渡すこともできます。このリダイレクトは、シンボル化されたパスパラメータとrequestオブジェクトを受け取ります。

get "/stories/:name", to: redirect { |path_params, req| "/articles/#{path_params[:name].pluralize}" }
get "/stories", to: redirect { |path_params, req| "/articles/#{req.subdomain}" }

デフォルトのリダイレクトは、HTTPステータス「301 "Moved Permanently"」のリダイレクトになる点にご注意ください。一部のWebブラウザやプロキシサーバーはこの種のリダイレクトをキャッシュすることがあり、その場合リダイレクト前の古いページにはアクセスできなくなります。次のように:statusオプションを使うことでレスポンスのステータスを変更できます。

get "/stories/:name", to: redirect("/articles/%{name}", status: 302)

どの場合であっても、ホスト(http://www.example.comなど)が指定されていない場合は、Railsは(以前のリクエストではなく)現在のリクエストから詳細を取得します。

3.14 Rackアプリケーションにルーティングする

:toオプションに'articles#index'ArticlesControllerクラスのindexアクションに対応する)のような文字列を指定する代わりに、任意のRackアプリケーションをマッチャーのエンドポイントとして指定できます。

match "/application.js", to: MyRackApp, via: :all

Railsルーターは、MyRackAppcallに応答して[status, headers, body]を返す限り、ルーティング先がRackアプリケーションであるかコントローラのアクションであるかを区別しません。これによって、適切と考えられるすべてのHTTP verbをRackアプリケーションで扱えるようになるので、これはvia: :allの適切な利用法です。

参考までに、'articles#index'は実際にはArticlesController.action(:index)という形に展開されます。これは有効なRackアプリケーションを返します。

procやlambdaはcallに応答するオブジェクトなので、たとえばヘルスチェックで用いるルーティングをget '/health', to: ->(env) { [204, {}, ['']] }のように極めてシンプルにインライン実装できます。

Rackアプリケーションをマッチャーのエンドポイントとして指定すると、それを受け取るRackアプリケーションのルーティングは変更されない点にご留意ください。以下のルーティングでは、Rackアプリケーションは/adminへのルーティングを期待するべきです。

match "/admin", to: AdminApp, via: :all

Rackアプリケーションがrootパスでリクエストを受け取るようにしたい場合は、mountを使います。

mount AdminApp, at: "/admin"

3.15 rootを使う

rootメソッドを使うことで、'/'によるルーティング先を指定できます。

root to: "pages#main"
root "pages#main" # 上の省略形

rootルーティングは、ルーティングファイルの冒頭に記述するのが一般的です(最初にマッチする必要があるため)。

rootルーティングがデフォルトで処理するのはGETリクエストですが、それ以外のHTTP verbを指定することも一応可能です(例: root "posts#index", via: :post)。

rootルーティングは、以下のように名前空間やスコープの内側でも指定できます。

root to: "home#index"

namespace :admin do
  root to: "admin#index"
end

上は、/adminAdminControllerindexアクションにマッチし、/HomeControllerindexアクションにマッチします。

3.16 Unicode文字列をルーティングで使う

Unicode文字列を以下のようにルーティングで直接使うこともできます。

get "こんにちは", to: "welcome#index"

3.17 ダイレクトルーティング(Direct routes)

directを呼び出すことで、カスタムURLヘルパーを次のように直接作成できます。

direct :homepage do
  "http://www.rubyonrails.org"
end

# >> homepage_url
# => "http://www.rubyonrails.org"

このブロックの戻り値は、必ずurl_forメソッドで有効な1個の引数にしなければなりません。これによって、有効な「文字列URL」「ハッシュ」「配列」「Active Modelインスタンス」「Active Modelクラス」のいずれか1つを渡せるようになります。

direct :commentable do |model|
  [ model, anchor: model.dom_id ]
end

direct :main do
  { controller: "pages", action: "index", subdomain: "www" }
end

# >> main_url
# => "http://www.example.com/pages"

3.18 resolveを使う

resolveメソッドを使うと、モデルのポリモーフィックなマッピングを次のようにカスタマイズできます。

resource :basket

resolve("Basket") { [:basket] }
<%= form_with model: @basket do |form| %>
  <!-- basket form -->
<% end %>

上のコードは、通常の/baskets/:idではなく、単数形の/basketというURLを生成します。

4 リソースフルルーティングをカスタマイズする

ほとんどの場合、resourcesで生成されるデフォルトのルーティングやヘルパーで用は足りますが、ルーティングを何らかの方法でカスタマイズしたくなることもあります。Railsでは、リソースフルルーティングやヘルパーをカスタマイズするためのさまざまな方法が用意されています。このセクションでは、利用可能なオプションについて詳しく説明します。

4.1 利用するコントローラを指定する

:controllerオプションは、リソースで使うコントローラを以下のように明示的に指定します。

resources :photos, controller: "images"

上のルーティングは、/photosで始まるパスを認識しますが、ルーティング先をImagesコントローラにします。

HTTP verb パス コントローラ#アクション 名前付きルーティングヘルパー
GET /photos images#index photos_path
GET /photos/new images#new new_photo_path
POST /photos images#create photos_path
GET /photos/:id images#show photo_path(:id)
GET /photos/:id/edit images#edit edit_photo_path(:id)
PATCH/PUT /photos/:id images#update photo_path(:id)
DELETE /photos/:id images#destroy photo_path(:id)

名前空間内のコントローラは以下のように直接指定できます。

resources :user_permissions, controller: "admin/user_permissions"

上はAdmin::UserPermissionsControllerのインスタンスにルーティングされます。

ここでサポートされている記法は、/で区切る「ディレクトリ記法」のみです。コントローラをRubyの定数表記法(controller: "Admin::UserPermissions"など)で指定する記法はサポートされていません。

4.2 制限をidで指定する

:constraintsオプションを使うと、暗黙で使われるidに対して以下のように必須のフォーマットを指定できます。

resources :photos, constraints: { id: /[A-Z][A-Z][0-9]+/ }

上の宣言は:idパラメータに制限を加え、指定した正規表現にのみマッチするようにします。上のルーティング例では/photos/1のようなパスにはマッチしなくなり、代わりに/photos/RR27のようなパスにマッチするようになります。

以下のようにブロック形式を使うことで、1つの制限を多数のルーティングに対してまとめて指定することも可能です。

constraints(id: /[A-Z][A-Z][0-9]+/) do
  resources :photos
  resources :accounts
end

このコンテキストでは、リソースフルでないルーティングのより高度な制限セクションで説明した方法も利用できます。

デフォルトでは:idパラメータにドット.を渡せません。ドットはフォーマット済みルーティングでは区切り文字として使われるためです。どうしても:id内でドットを使いたい場合は、デフォルト設定を上書きする制限を与えます。たとえばid: /[^\/]+/とすると、スラッシュ以外のすべての文字が使えます。

4.3 名前付きルーティングヘルパーをオーバーライドする

:asオプションを使うと、ルーティングヘルパーのデフォルトの命名方法を以下のように上書きしてルーティングヘルパー名を変えられます。

resources :photos, as: "images"

上のルーティングは/photosにマッチし、リクエストを通常どおりPhotosControllerにルーティングしますが、ヘルパーには:asオプションで指定した値を用いてimages_pathなどの名前を付けます。

HTTP verb パス コントローラ#アクション 名前付きルーティングヘルパー
GET /photos photos#index images_path
GET /photos/new photos#new new_image_path
POST /photos photos#create images_path
GET /photos/:id photos#show image_path(:id)
GET /photos/:id/edit photos#edit edit_image_path(:id)
PATCH/PUT /photos/:id photos#update image_path(:id)
DELETE /photos/:id photos#destroy image_path(:id)

4.4 neweditのパス名をリネームする

:path_namesオプションを使うと、パスに含まれているデフォルトの"new"セグメントや"edit"セグメントをオーバーライドできます。

resources :photos, path_names: { new: "make", edit: "change" }

これにより、ルーティングで/photos/newの代わりに/photos/make/photos/1/editの代わりに/photos/1/changeというパスを認識できるようになります。

このオプションを指定しても、実際のルーティングヘルパーやコントローラアクション名が変更されるわけではありません。表示されるパスにはnew_photo_pathヘルパーとedit_photo_pathヘルパーが引き続き存在し、ルーティング先もnewアクションとeditアクションのままです。

この:path_namesオプションをブロック付きscopeで使うと、スコープ内のすべてのルーティングに対してパス名を変更できます。

scope path_names: { new: "make" } do
  # ブロック内の残りすべてのルーティング
end

4.5 名前付きルーティングヘルパーに:asでプレフィックスを追加する

以下のように:asオプションを使うことで、Railsがルーティングに対して生成する名前付きルーティングヘルパー名の冒頭に文字を追加(プレフィックス)できます。パススコープを使うルーティング同士での名前の衝突を避けたい場合にお使いください。

scope "admin" do
  resources :photos, as: "admin_photos"
end

resources :photos

上のようにas:を使うと、/admin/photosのルーティングヘルパーが、photos_pathnew_photos_pathなどからadmin_photos_pathnew_admin_photo_pathなどに変更されます。 as: "admin_photos"をスコープ付きresources :photosに追加しない場合は、スコープなしのresources :photosはルーティングヘルパーを持つことができません。

ルーティングヘルパーのグループにまとめてプレフィックスを追加するには、以下のようにscopeメソッドで:asオプションを使います。

scope "admin", as: "admin" do
  resources :photos, :accounts
end

resources :photos, :accounts

上のルーティングは、先ほどと同様に/adminのスコープ付きリソースヘルパーをadmin_photos_pathadmin_accounts_pathに変更し、さらにスコープなしのリソースphotos_pathaccounts_pathも利用可能になります。

namespaceスコープを使うと、:module:pathプレフィックスに加えて:asも自動的に追加されます。

4.6 ネストしたリソース内で:asを使う

:asオプションを使うと、以下のようにネストしたルーティング内のリソースルーティングヘルパー名もオーバーライドできます。

resources :magazines do
  resources :ads, as: "periodical_ads"
end

これにより、デフォルトのmagazine_ads_urledit_magazine_ad_pathの代わりに、magazine_periodical_ads_urledit_magazine_periodical_ad_pathなどのルーティングヘルパーが作成されます。

4.6.1 パラメトリックスコープ

名前付きパラメータを持つルーティングにプレフィックスを追加できます。

scope ":account_id", as: "account", constraints: { account_id: /\d+/ } do
  resources :articles
end

上のルーティングは/1/articles/9のようなパスを提供します。パスのaccount_id部分をparams[:account_id]の形でコントローラ、ヘルパー、ビューで参照できるようになります。

また、account_をプレフィックスとするパスヘルパーやURLヘルパーも生成され、これらにオブジェクトを渡すこともできます。

account_article_path(@account, @article) # => /1/article/9
url_for([@account, @article])            # => /1/article/9
form_with(model: [@account, @article])   # => <form action="/1/article/9" ...>

asオプションは必須ではありませんが、これがないとurl_for([@account, @article])や、url_forに依存するヘルパー(form_withなど)の評価時にエラーが発生します。

4.7 作成されるルーティングを制限する

resourcesを使うと、デフォルトで7つのアクション(indexshownewcreateeditupdatedestroy)へのルーティングを作成します。作成されるルーティングは、:onlyオプションや:exceptオプションで制限をかけられます。

:onlyオプションは、指定したルーティングだけを生成するよう指示します。

resources :photos, only: [:index, :show]

これで、/photos/photos/:idへのGETリクエストは成功し、/photosへのPOSTリクエストは失敗します。

:exceptオプションは逆に、生成しないルーティング(またはルーティングのリスト)を指定します。

resources :photos, except: :destroy

この場合、destroy/photos/:idへのDELETEリクエスト)を除いた通常のルーティングが生成されます。

アプリケーションでRESTfulルーティングが多数使われている場合は、適宜:only:exceptを用いて実際に必要なルーティングのみを生成することで、メモリ使用量の節約と未使用のルーティングの削減によるルーティング処理の速度向上が見込めます。

4.8 パスを変更する

scopeメソッドを使うことで、resourcesによって生成されるデフォルトのパス名を変更できます。

scope(path_names: { new: "neu", edit: "bearbeiten" }) do
  resources :categories, path: "kategorien"
end

上のようにすることで、以下のようなCategoriesコントローラへのルーティングが作成されます。

HTTP verb パス コントローラ#アクション 名前付きルーティングヘルパー
GET /kategorien categories#index categories_path
GET /kategorien/neu categories#new new_category_path
POST /kategorien categories#create categories_path
GET /kategorien/:id categories#show category_path(:id)
GET /kategorien/:id/bearbeiten categories#edit edit_category_path(:id)
PATCH/PUT /kategorien/:id categories#update category_path(:id)
DELETE /kategorien/:id categories#destroy category_path(:id)

4.9 リソースの単数形を指定する

リソースの単数形の名前をオーバーライドしたい場合、以下のようにActiveSupport::Inflectorinflectionsで活用形ルールを追加します。

ActiveSupport::Inflector.inflections do |inflect|
  inflect.irregular "tooth", "teeth"
end

4.10 ルーティングのデフォルトパラメータidをリネームする

:paramオプションを指定することで、以下のようにデフォルトのidパラメータ名を別の名前に変更できます。

resources :videos, param: :identifier

これにより、params[:id]の代わりにparams[:identifier]が使われるようになります。

    videos GET  /videos(.:format)                  videos#index
           POST /videos(.:format)                  videos#create
 new_video GET  /videos/new(.:format)              videos#new
edit_video GET  /videos/:identifier/edit(.:format) videos#edit
Video.find_by(id: params[:identifier])

# ↓変更前
Video.find_by(id: params[:id])

関連するモデルのActiveRecord::Base#to_paramを以下のようにオーバーライドしてURLを作成できます。

class Video < ApplicationRecord
  def to_param
    identifier
  end
end
irb> video = Video.find_by(identifier: "Roman-Holiday")
irb> edit_video_path(video)
=> "/videos/Roman-Holiday/edit"

5 ルーティングを調べる

Railsには、ルーティングを調べるさまざまな機能(inspection)とテスト機能が備わっています。

5.1 既存のルールを一覧表示する

現在のアプリケーションで利用可能なルーティングをすべて表示するには、サーバーがdevelopment環境で動作している状態でhttp://localhost:3000/rails/info/routesをブラウザで開きます。ターミナルでbin/rails routesコマンドを実行しても同じ結果を得られます。

どちらの方法を使った場合でも、config/routes.rbファイルに記載された順にルーティングが表示されます。1つのルーティングについて以下の情報が表示されます。

  • ルーティング名(あれば)
  • 使われるHTTP verb(そのルーティングが一部のHTTP verbに応答しない場合)
  • マッチするURLパターン
  • そのルーティングで使うパラメータ

以下は、あるRESTfulルーティングに対してbin/rails routesを実行した結果から抜粋したものです。

    users GET    /users(.:format)          users#index
          POST   /users(.:format)          users#create
 new_user GET    /users/new(.:format)      users#new
edit_user GET    /users/:id/edit(.:format) users#edit

一番左のルーティング名(上のnew_userなど)は、生成されるルーティングヘルパー名のベースとみなせます。 ルーティングヘルパー名を取得するには、このルーティング名にサフィックス(_path_url)を追加します(例: new_user_path)。

--expandedオプションを指定することで、ルーティングテーブルのフォーマットを以下のような詳細モードに切り替えることも可能です。

$ bin/rails routes --expanded

--[ Route 1 ]----------------------------------------------------
Prefix            | users
Verb              | GET
URI               | /users(.:format)
Controller#Action | users#index
--[ Route 2 ]----------------------------------------------------
Prefix            |
Verb              | POST
URI               | /users(.:format)
Controller#Action | users#create
--[ Route 3 ]----------------------------------------------------
Prefix            | new_user
Verb              | GET
URI               | /users/new(.:format)
Controller#Action | users#new
--[ Route 4 ]----------------------------------------------------
Prefix            | edit_user
Verb              | GET
URI               | /users/:id/edit(.:format)
Controller#Action | users#edit

5.2 ルーティングを検索する

-g(grepオプション)でルーティングを絞り込めます。URLヘルパー名、HTTP verb、URLパスのいずれかに部分マッチするルーティングが出力されます。

$ bin/rails routes -g new_comment
$ bin/rails routes -g POST
$ bin/rails routes -g admin

特定のコントローラに対応するルーティングだけを表示したい場合は、-cオプションでコントローラ名を指定します。

$ bin/rails routes -c users
$ bin/rails routes -c admin/users
$ bin/rails routes -c Comments
$ bin/rails routes -c Articles::CommentsController

bin/rails routesコマンド出力を読みやすく表示するには、出力が折り返されなくなるまでターミナルウィンドウを拡大するか、--expandedオプションを指定します。

5.3 使われていないルーティングを表示する

--unusedオプションを指定することで、アプリケーションで使われていないルーティングをスキャンできます。Railsにおける「未使用」ルーティングとは、config/routes.rbファイルには定義されているが、アプリケーション内のどのコントローラーアクションやビューからも参照されていないルーティングのことです。

$ bin/rails routes --unused
Found 8 unused routes:

     Prefix Verb   URI Pattern                Controller#Action
     people GET    /people(.:format)          people#index
            POST   /people(.:format)          people#create
 new_person GET    /people/new(.:format)      people#new
edit_person GET    /people/:id/edit(.:format) people#edit
     person GET    /people/:id(.:format)      people#show
            PATCH  /people/:id(.:format)      people#update
            PUT    /people/:id(.:format)      people#update
            DELETE /people/:id(.:format)      people#destroy

5.4 Railsコンソールでルーティングにアクセスする

Railsコンソール内では、Rails.application.routes.url_helpersでルーティングヘルパーにアクセスできます。ルーティングヘルパーは、appオブジェクト経由でもアクセスできます。

irb> Rails.application.routes.url_helpers.users_path
=> "/users"
irb> user = User.first
=> #<User:0x00007fc1eab81628
irb> app.edit_user_path(user)
=> "/users/1/edit"

6 ルーティングをテストする

Railsでは、テストをシンプルに書くための3つの組み込みアサーションが用意されています。

6.1 assert_generatesアサーション

assert_generatesは、特定のオプションの組み合わせを使った場合に特定のパスが生成されること、そしてそれらがデフォルトのルーティングでもカスタムルーティングでも使えることをテストするアサーション(assertion: 主張)です。

assert_generates "/photos/1", { controller: "photos", action: "show", id: "1" }
assert_generates "/about", controller: "pages", action: "about"

6.2 assert_recognizesアサーション

assert_recognizesassert_generatesと逆方向のテスティングを行います。与えられたパスが認識可能であること、アプリケーションの特定の場所にルーティングされることをテストするアサーションです。

assert_recognizes({ controller: "photos", action: "show", id: "1" }, "/photos/1")

:method引数でHTTP verbを指定することもできます。

assert_recognizes({ controller: "photos", action: "create" }, { path: "photos", method: :post })

6.3 assert_routingアサーション

assert_routingアサーションは、assert_generatesassert_recognizesの機能を組み合わせたもので、ルーティングを2つの観点(与えられたパスによってオプションが生成されること、そのオプションによって元のパスが生成されること)でまとめてチェックします。

assert_routing({ path: "photos", method: :post }, { controller: "photos", action: "create" })

7 巨大なルーティングファイルを分割する

ルーティングが数千にもおよぶ大規模アプリケーションでは、複雑なconfig/routes.rbファイル1個だけでは読みづらくなります。Railsでは、このような巨大routes.rbファイルをdrawマクロで小さなルーティングファイルに分割する方法が提供されています。

たとえばadmin.rbにはadmin関連のルーティングをすべて含め、API関連リソースのルーティングはapi.rbファイルで記述するといったことが可能です。

# config/routes.rb

Rails.application.routes.draw do
  get "foo", to: "foo#bar"

  draw(:admin) # `config/routes/admin.rb`にある別のルーティングファイルを読み込む
end
# config/routes/admin.rb

namespace :admin do
  resources :comments
end

Rails.application.routes.draw自身の中でdraw(:admin)を呼び出すと、指定の引数と同じ名前のルーティングファイル(この例ではadmin.rb)の読み込みを試行します。 このファイルは、config/routesディレクトリの下か、任意のサブディレクトリ(config/routes/admin.rbconfig/routes/external/admin.rbなど)に存在する必要があります。

admin.rbルーティングファイル内でも通常のルーティングDSLを利用できますが、Rails.application.routes.drawブロックで囲んではいけませんRails.application.routes.drawブロックは、メインのconfig/routes.rbファイルでのみ使われるべきです。

ルーティングファイルの分割機能は、本当に必要になるまでは使ってはいけません。ルーティングファイルが複数になると、1つのファイルでルーティングを探すときよりも手間がかかります。ほとんどのアプリケーションでは(ルーティングが数百件にのぼる場合であっても)、ルーティングファイルを1つにまとめておく方が開発者にとって扱いやすくなります。RailsのルーティングDSLでは、namespacescopeでルーティングを体系的に分割する方法がすでに提供されています。

フィードバックについて

Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。

原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨

本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。

Railsガイド運営チーム (@RailsGuidesJP)

支援・協賛

Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。

  1. Star
  2. このエントリーをはてなブックマークに追加