Rails のルーティング

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

このガイドの内容:

目次

  1. Railsルーターの目的
  2. リソースベースのルーティング: Railsのデフォルト
  3. リソースフルでないルーティング
  4. リソースフルルーティングをカスタマイズする
  5. ルーティングの調査とテスト

1 Railsルーターの目的

Railsのルーターは受け取ったURLを認識し、適切なコントローラ内アクションやRackアプリケーションに割り当てます。ルーターは、ビューでこれらのパスやURLを直接ハードコードすることを避けるためにパスやURLを生成することもできます。

1.1 URLを実際のコードに割り振る

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

GET /patients/17

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

get '/patients/:id', to: 'patients#show'

このリクエストはpatientsコントローラのshowアクションに割り当てられ、paramsには{ id: '17' }ハッシュが含まれています。

Railsではコントローラ名にスネークケースを使います。たとえばMonsterTrucksControllerのような複合語のコントローラを使う場合は、monster_trucks#showのように指定します。

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

パスやURLを生成することもできます。たとえば、上のルーティングが以下のように変更されたとします。

get '/patients/:id', to: 'patients#show', as: 'patient'

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

@patient = Patient.find(params[:id])

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

<%= link_to 'Patient Record', patient_path(@patient) %>

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

1.3 Railsルーターを設定する

アプリケーションやエンジンのルーティングはconfig/routes.rbファイルの中に存在し、通常以下のような感じになっています。

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のあらゆる機能を用いてルーティングを定義できます。ただし変数名には注意が必要です。ルーターのDSLメソッド名と変数名と衝突する可能性があります。

ルーティング定義をラップするRails.application.routes.draw do ... endブロックは、ルーターDSLのスコープを確定するのに不可欠なので、削除してはいけません。

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

リソースベースのルーティング (以下リソースルーティング) を使うことで、リソースベースで構成されたコントローラに対応する共通のルーティングを手軽に宣言できます。リソースフルなルーティングを宣言することで、コントローラのindexshowneweditcreateupdatedestroyアクションを個別に宣言しなくても1行で宣言が完了します。

2.1 Web上のリソース

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

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

DELETE /photos/17

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

resources :photos

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

2.2 CRUD、動詞、アクション

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

resources :photos

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

HTTP動詞 パス コントローラ#アクション 目的
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動詞とURLを使っているため、4種類のURL (/photos,/photos/new,/photos/:id,/photos/:id/edit) が7種類の異なるアクション (index/new/create/show/edit/update/destroy) に割り当てられています。

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

2.3 パスとURL用ヘルパー

リソースフルなルーティングを作成すると、アプリケーションのコントローラで多くのヘルパーが利用できるようになります。resources :photosというルーティングを例に取ってみましょう。

  • photos_path/photosを返します
  • new_photo_path/photos/newを返します
  • edit_photo_path(:id)/photos/:id/editを返します (edit_photo_path(10)であれば/photos/10/editが返されます)
  • photo_path(:id)/photos/:idを返します。 (photo_path(10)であれば/photos/10が返されます)

これらの_pathヘルパーには、それぞれに対応する_urlヘルパー (photos_urlなど) があります。_urlヘルパーは、_pathの前に現在のホスト名、ポート番号、パスのプレフィックスが追加されている点が異なります。

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

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

resources :photos, :books, :videos

上の記法は以下と完全に同一です。

resources :photos
resources :books
resources :videos

2.5 単数形リソース

ユーザーがページを表示する際にidを一切参照しないリソースが使われることがあります。たとえば、/profileでは常に「現在ログインしているユーザー自身」のプロファイルを表示し、他のユーザーidを参照する必要がないとします。このような場合には、単数形リソース (singular resource) を使ってshowアクションに (/profile/:idではなく) /profileを割り当てることができます。

get 'profile', to: 'users#show'

to:の引数に文字列を渡す場合はコントローラ#アクション形式であることが前提ですが、シンボルを使う場合は、to:オプションをaction:に置き換えるべきです。#なしの文字列を使う場合は、to:オプションをcontroller:に置き換えるべきです。

get 'profile', action: :show, controller: 'users'

以下に記述するリソースフルなルーティングは

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

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

HTTP動詞 パス コントローラ#アクション 目的
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リソースを削除する

単数形リソースは複数形のコントローラに割り当てられます。これは、同じコントローラで単数形のルーティング (/account) と複数形のルーティング (/accounts/45) を両方使いたい場合を想定しているためです。従って、resource :photoresources :photosのどちらも、単数形ルーティングと複数形ルーティングを両方作成し、同一のコントローラ (PhotosController) に割り当てられます。

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

  • new_geocoder_path/geocoder/newを返します
  • edit_geocoder_path/geocoder/editを返します
  • geocoder_path/geocoderを返します。

複数形リソースの場合と同様に、単数形リソースでも_pathヘルパーに対応する_urlヘルパーが使えます。_urlヘルパーは、_pathの前に現在のホスト名、ポート番号、パスのプレフィックスが追加されている点が異なります。

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

コントローラを名前空間によってグループ化することもできます。最もよく使われる名前空間といえば、多数の管理用コントローラ群をまとめるAdmin::名前空間でしょう。これらのコントローラをapp/controllers/adminディレクトリに配置し、ルーティングでこれらをグループ化できます。

namespace :admin do
  resources :articles, :comments
end

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

HTTP動詞 パス コントローラ#アクション 名前付きルーティングヘルパー
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)

例外的に、(/adminが前についていない) /articlesAdmin::ArticlesControllerにルーティングしたい場合は、以下のようにすることもできます。

scope module: 'admin' do
  resources :articles, :comments
end

以下のようにブロックを使わない記述も可能です。

resources :articles, module: 'admin'

逆に、/admin/articlesを (Admin::なしの) ArticlesControllerにルーティングしたい場合は、以下のようにします。

scope '/admin' do
  resources :articles, :comments
end

以下のようにブロックを使わない記述も可能です。

resources :articles, path: '/admin/articles'

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

HTTP動詞 パス コントローラ#アクション 名前付きルーティングヘルパー
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

上のルーティングによって、雑誌 (magazine) へのルーティングに加えて、広告 (ad) をAdsControllerにルーティングすることもできるようになりました。adへのURLにはmagazineもなければなりません。

HTTP動詞 パス コントローラ#アクション 目的
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つ削除する

ルーティングを作成すると、ルーティングヘルパーも作成されます。ヘルパーはmagazine_ads_urledit_magazine_ad_pathのような名前になります。これらのヘルパーは、最初のパラメータとしてMagazineモデルのインスタンスを1つ取ります (magazine_ads_url(@magazine))。

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つの階層すべてでオブジェクトを指定する必要があります。ネスティングが深くなるとルーティングが扱いにくくなる問題については、Jamis Buckの有名な 記事 を参照してください。JamisはRailsアプリケーション設計上の優れた経験則を提案しています。

リソースのネスティングは、ぜひとも1回にとどめて下さい。決して2回以上ネストするべきではありません。

2.7.2 「浅い」ネスト

前述したような深いネストを避けるひとつの方法として、コレクション (index/new/createのような、idを持たないアクション) だけを親のスコープの下で生成するという手法があります。このとき、メンバー (show/edit/update/destroyのような、idを必要とするアクション) をネストに含めないのがポイントです。これによりコレクションだけが階層化のメリットを受けられます。つまり、以下のように最小限の情報でリソースを一意に指定できるルーティングを作成するということです。

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

この方法は、ルーティングの記述を複雑にせず、かつ深いネストを作らないという絶妙なバランスを保っています。:shallowオプションを使うことで、上と同じ内容をさらに簡単に記述できます。

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

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

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

DSL (ドメイン固有言語) であるshallowメソッドをルーティングで使うと、すべてのネストが浅くなるように内側にスコープを1つ作成します。これによって生成されるルーティングは、最初の例と完全に同じです。

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

scopeメソッドには、「浅い」ルーティングをカスタマイズするためのオプションが2つあります。:shallow_pathオプションは、指定されたパラメータをメンバーのパスの冒頭にだけ追加します。

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

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

HTTP動詞 パス コントローラ#アクション 名前付きルーティングヘルパー
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オプションを使うと、指定されたパラメータを (パスではなく) 名前付きルーティングヘルパー名の冒頭に追加します。

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

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

HTTP動詞 パス コントローラ#アクション 名前付きルーティングヘルパー
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 :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

concernはルーティング内のどの場所にでも配置できます。scopenamespace呼び出しでは以下のように利用できます。

namespace :articles do
  concerns :commentable
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) %>

複数のオブジェクトが集まったセットに対して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 %>

それ以外のアクションであれば、配列の最初の要素にアクション名を挿入するだけで済みます。

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

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

2.10 RESTfulなアクションをさらに追加する

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

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

メンバー (member) ルーティングを追加したい場合は、memberブロックをリソースブロックに1つ追加します。

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

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

memberルーティングブロックの内側では、認識させるHTTP動詞をルーティング名ごとに指定します。指定可能なHTTP動詞はgetpatchputpostdeleteです。memberルーティングが1つだけしかない場合は、以下のようにルーティングで:onオプションを指定することでブロックを省略できます。

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

:onオプションを省略しても同様のmemberルーティングが生成されます。この場合リソースidの値の取得にparams[:id]ではなくparams[:photo_id]を使う点が異なります。ルーティングヘルパーも、preview_photo_urlphoto_preview_urlに、preview_photo_pathphoto_preview_pathにそれぞれリネームされます。

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

ルーティングにコレクション (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

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

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ルーティングヘルパーも同時に作成されます。

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

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

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

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

シンプルルーティングは、特に従来形式のURLを新しいRailsのアクションに割り当てることがずっと簡単に行えるようになります。

3.1 パラメータの割り当て

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

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

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

3.2 動的なセグメント

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

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

ブラウザからの/photos/1/2パスはPhotosControllershowアクションに割り当てられます。params[:id]には"1"params[: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'

ブラウザからのリクエストで/photos/1?user_id=2というパスが渡されると、Photosコントローラの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アクションに割り当てられます。

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

defaults format: :json do
  resources :photos
end

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

3.6 名前付きルーティング

:asオプションを使うと、どんなルーティングにも名前を指定できます。

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

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

この方法を使って、リソースとして定義されているルーティングを以下のように上書きすることもできます。

get ':username', to: 'users#show', as: :user

上のルーティングではuser_pathメソッドが生成され、コントローラ・ヘルパー・ビューでそれぞれ使えるようになります。このメソッドは、/bobのようなユーザー名を持つルーティングに移動します。Usersコントローラのshowアクションの内部でparams[:username]にアクセスすると、ユーザー名を取り出すことができます。パラメータ名を:usernameにしたくない場合は、ルーティング定義の:usernameの部分を変更してください。

3.7 HTTP動詞を制限する

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

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

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

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

1つのアクションにGETリクエストとPOSTリクエストを両方ルーティングすると、セキュリティに影響する可能性があります。本当に必要な理由がない限り、1つのアクションにすべてのHTTP動詞をルーティングすることは避けてください。

RailsではGETのCSRFトークンをチェックしません。決してGETリクエストでデータベースに書き込んではいけません。詳しくはセキュリティガイドのCSRF対策を参照してください。

3.8 セグメントを制限する

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

get 'photos/:id', to: 'photos#show', constraints: { id: /[A-Z]\d{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/}

対象となるルーティングはすべて初めからアンカーされているので、このようなアンカー表現を使う必要はないはずです。

たとえば以下のルーティングでは、ルート (root) 名前空間を共有する際にarticlesに対してto_param1-hello-worldのように数字で始まる値だけが使えるようになっており、usersに対してto_paramdavidのように数字で始まらない値だけが使えるようになっています。

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

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

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

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

get 'photos', constraints: {subdomain: 'admin'}

ブロックフォームに対して制限を指定することもできます。

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

リクエストベースの制限は、Requestオブジェクトに対してあるメソッドを呼び出すことで実行されます。メソッド呼び出し時にハッシュキーと同じ名前をメソッドに渡し、返された値をハッシュ値と比較します。従って、制限された値は、対応するRequestオブジェクトメソッドが返す型と一致する必要があります。たとえば、constraints: { subdomain: 'api' }という制限はapiサブドメインに期待どおりマッチしますが、constraints: { subdomain: :api }のようにシンボルを使った場合はapiサブドメインに一致しません。request.subdomainが返す'api'は文字列型であるためです。

formatの制限には例外があります。これはRequestオブジェクトのメソッドですが、すべてのパスに含まれる暗黙的なオプションのパラメータでもあります。formatの制限よりセグメント制限が優先されます。たとえば、get 'foo'、constraints: { format: 'json' }GET /fooと一致します。これはデフォルトでformatがオプションであるためです。しかし、次のようにlambdaを使うことができます。get 'foo', constraints: lambda { |req| req.format == :json } このルーティング指定は明示的な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.11 ルーティンググロブとワイルドカードセグメント

ルーティンググロブ (route globbing) とはワイルドカード展開のことであり、ルーティングのある位置から下のすべての部分に特定のパラメータをマッチさせる際に使います。例:

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

上のルーティングは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'が保存されます。

'/foo/bar.json'をリクエストするとparams[:pages]には'foo/bar'がJSONリクエストフォーマットで保存されます。Rails 3.0.xの動作に戻したい場合は、以下のようにformat: falseを指定することができます。

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

このセグメントフォーマットを必須にしたい場合は、以下のようにformat: trueを指定します。

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

3.12 リダイレクト

ルーティングで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など) がURLの冒頭で指定されていない場合は、Railsは (以前のリクエストではなく) 現在のリクエストから詳細を取得します。

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

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

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

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

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

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

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

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

 mount AdminApp, at: '/admin'

3.14 rootを使う

rootメソッドを使うことで、Railsがルート'/'とすべき場所を指定できます。

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

rootルーティングは、ルーティングファイルの先頭に記述してください。rootは最もよく使用されるルーティングであり、最初にマッチする必要があるからです。

rootルーティングがアクションに渡せるのはGETリクエストだけです。

名前空間やスコープの内側にrootを置くこともできます。例:

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

root to: "home#index"

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

Unicode文字列をルーティングで直接使うこともできます。例:

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

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

カスタム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

3.17 resolveを使う

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

resource :basket

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

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

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

ほとんどの場合、resources :articlesのような指定を行ってデフォルトのルーティングやヘルパーを生成することで用は足りますが、もう少しルーティングをカスタマイズしたくなることもあります。Railsでは、リソースフルなヘルパーの一般的などの部分であっても事実上自由にカスタマイズ可能です。

4.1 使うコントローラを指定する

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

resources :photos, controller: 'images'

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

HTTP動詞 パス コントローラ#アクション 名前付きルーティングヘルパー
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)

このリソースへのパスを生成するにはphotos_pathnew_photo_pathなどを使っててください。

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

resources :user_permissions, controller: 'admin/user_permissions'

上はAdmin::UserPermissionsにルーティングされます。

ここでサポートされている記法は、/で区切る「ディレクトリ式」のみです。Rubyの定数表記法 (controller: 'Admin::UserPermissions'など) をコントローラに対して使うと、ルーティングで問題が生じ、警告が出力される可能性があります。

4.2 制限を指定する

: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で始まるブラウザからのパスを認識し、このリクエストをPhotosコントローラにルーティングしますが、ヘルパーの命名に:asオプションの値が使用されます。

HTTP動詞 パス コントローラ#アクション 名前付きルーティングヘルパー
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 newセグメントやeditセグメントをオーバーライドする

:path_namesオプションを使うと、パスに含まれている、自動生成された"new"セグメントや"edit"セグメントをオーバーライドできます。

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

これにより、ルーティングで以下のようなパスが認識できるようになります。

/photos/make
/photos/1/change

このオプションを指定しても、実際のアクション名が変更されるわけではありません。変更後のパスを使っても、ルーティング先は依然としてnewアクションとeditアクションのままです。

このオプションによる変更をすべてのルーティングに統一的に適用したくなった場合は、スコープを使えます。

scope path_names: { new: 'make' } do
  # 残りすべてのルーティング
end

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

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

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

resources :photos

上のルーティングでは、admin_photos_pathnew_admin_photo_pathなどのルーティングヘルパーが生成されます。

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

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

resources :photos, :accounts

上によって、admin_photos_pathadmin_accounts_pathなどのルーティングが生成されます。これらは/admin/photos/admin/accountsにそれぞれ割り当てられます。

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

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

scope ':username' do
  resources :articles
end

上のルーティングにより、/bob/articles/1のような形式のURLを使えるようになります。さらに、コントローラ、ヘルパー、ビューのいずれにおいても、このパスのusernameの部分に相当する文字列 (この場合であればbob) をparams[:username]で参照できます。

4.6 ルーティングの作成を制限する

Railsは、アプリケーション内のすべてのRESTfulルーティングに対してデフォルトで7つのアクション (index、show、new、create、edit、update、destroy) へのルーティングを作成します。:onlyオプションや:exceptオプションを使うことで、これらのルーティングを微調整できます。:onlyオプションは、指定されたルーティングだけを生成するよう指示します。

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

これで、/photosへのGETリクエストは成功し、/photos へのPOSTリクエスト (通常であればcreateアクションにルーティングされます) は失敗します。

:exceptオプションは逆に、指定したルーティングのみを生成 しない よう指示します。

resources :photos, except: :destroy

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

アプリケーションでRESTfulルーティングが多数使用されているのであれば、それらに適宜:only:exceptを使って、本当に必要なルーティングのみを生成することで、メモリ使用量の節約とルーティングプロセスの速度向上が見込めます。

4.7 パスを変更する

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

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

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

HTTP動詞 パス コントローラ#アクション 名前付きルーティングヘルパー
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.8 「単数形のフォーム」をオーバーライドする

あるリソースの「単数形のフォーム」を定義したい場合、Inflectorに活用形ルールを追加します。

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

4.9 名前付きリソースで:asを使う

:asを使うと、ネストしたルーティングヘルパー内のリソース用に自動生成された名前をオーバーライドできます。例:

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

上のルーティングによって、magazine_periodical_ads_urledit_magazine_periodical_ad_pathなどのルーティングヘルパーが生成されます。

4.10 名前付きルーティングのパラメータをオーバーライドする

:paramオプションは、デフォルトのリソース識別子:id (ルーティングを生成するために使用される動的なセグメントの名前) をオーバーライドします。params[<:param>]を使って、コントローラからそのセグメントにアクセスできます。

resources :videos, param: :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(identifier: params[:identifier])

関連するモデルの ActiveRecord::Base#to_param をオーバーライドしてURLを作成することができます。

class Video < ApplicationRecord
  def to_param
    identifier
  end
end

video = Video.find_by(identifier: "Roman-Holiday")
edit_video_path(video) # => "/videos/Roman-Holiday/edit"

5 ルーティングの調査とテスト

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

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

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

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

  • ルーティング名 (あれば)
  • 使用されているHTTP動詞 (そのルーティングがすべてのHTTP動詞に応答するのでない場合)
  • マッチするURLパターン
  • そのルーティングで使うパラメータ

以下は、あるRESTfulルーティングに対して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

--expandedオプションを用いて、ルーティングテーブルのフォーマットを以下のようなexpandedモードに切り替えることもできます。

$ 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

-g(grepオプション)を使ってルーティングを検索できます。URLヘルパー名、HTTP動詞、URLパスのいずれかに部分マッチするルーティングが出力されます。

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

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

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

折り返しが発生しないぐらいに十分大きなサイズのターミナルを使えるのであれば、rails routesコマンドの出力の方がおそらく読みやすいでしょう。

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

アプリケーションの他の部分と同様、ルーティング部分もテスティング戦略に含めておくべきでしょう。Railsでは、テスティングを容易にするために3つのビルトインアサーション が用意されています。

  • assert_generates
  • assert_recognizes
  • assert_routing
5.2.1 assert_generatesアサーション

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

assert_generates '/photos/1', { controller: 'photos', action: 'show', id: '1' }
assert_generates '/about', controller: 'pages', action: 'about'
5.2.2 assert_recognizesアサーション

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

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

引数で:methodを使ってHTTP動詞を指定することもできます。

assert_recognizes({ controller: 'photos', action: 'create' }, { path: 'photos', method: :post })
5.2.3 assert_routingアサーション

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

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

支援・協賛

Railsガイドは下記のサポーターから継続的な支援を受けています。