更新情報
- 2014/03/03: 初版公開
- 2020/11/20: Rails 6で確認および更新
こんにちは、hachi8833です。「Railsのルーティングを極める」の後編です。今回はRails 4.0.3 + Ruby 2.1.1の環境で動作確認しています。
⚓ Railsのルーティング(routes)を極める
2012/03(baba)
⚓ resourcesとネスト
Railsのルーティング記法の基本は、複数形のresourcesメソッドと単数形のresourceメソッドです。また、Railsのルーティングにはネストを含む多くのオプションがあり、自由度が飛躍的に高まっています。
以下の2つのルーティングは、ネストしていないシンプルなresourcesルーティングです。prefecturesとarticlesは、いずれもコントローラに合わせて複数形で書く点にご注意ください。
resources :prefectures
resources :articles
rails routesしてみると、prefecturesとarticlesそれぞれについてRESTfulかつ標準的なアクション(index、create・new・edit・show・update・destroy)を網羅したなルーティングが一気に生成されています。
ところで、せっかくなのでRails 4.0以降で使えるルーティング表示機能でも見てみましょう。development環境でRailsを起動して、ブラウザでhttp://localhost:3000/rails/info/routesを開くと以下のように表示されます。
名前付きルートもHelper列にわかりやすく表示され、[Path]と[Url]をクリックすれば名前付きルートの*_pathと*_urlを切り替えて表示するという細かい芸もやってくれます。
参考までに、最近のRailsでは以下のように表示が洗練されています(6.0.3.4で確認)。
なお名前付きルートのうち、*_pathはドメイン名から下のパス、*_urlはhttp://などから始まるフルパスであることは前編でも説明いたしました。生成された名前付きルートをrails consoleで確認するには、app.に続けて名前付きルートを入力してみます。
「複数形にはidはなく、単数形にはidがある」と覚えておくとよいでしょう。
このようにコードで名前付きルートを使うことで、生のURLをコードに書かずに済みます。
名前付きルートはカスタムで指定することもできますが、なるべくこのように標準的なものをRailsに生成させる方が楽ですし混乱せずに済みます。
では、このうちprefecturesの下でcitiesとcompaniesをネストさせてみましょう。
resources :prefectures do
resources :cities do
resources :companies
end
end
このときのルーティングテーブルは以下のようになります。
見てのとおり、prefecturesのルーティングに加え、prefectures/cities、そしてprefectures/city/companiesという階層が追加されました。いずれも複数形の「resources」を指定しているので、prefectures, city, companyにはidがあります。
このときの名前付きルートは次のようになります。idの部分には適当な数字を入れてあります。
⚓ 単数リソース
上では複数形のresourcesを使用してid付きのルーティングを生成しましたが、単純なページへのルーティングのようにidが不要な場合は単数形のresourceを指定することができます。
仮にprefectureでidが不要だとすると、以下のように単数形のresourceを指定し、prefectureも単数で書きます。
resource :prefecture
このときのルーティングは以下のようになります。
見てのとおり、Pathにid:が含まれなくなり、indexアクションもなくなりました。
なお、この記述「resource」も「prefecture」も単数ですが、これによって指定されるコントローラは「prefectures」と複数形になっていることにご注意ください。御存知のとおり、Railsではコントローラ名を複数形で書くことになっています。
名前付きルートを確認します。なお、無効なはずのidをわざと付けてみると、妙なパスが生成されました。
⚓ 複数リソースと単数リソースのネスト
今度は複数リソースと単数リソースの組み合わせの例を示します。ユーザーは複数いるのが普通なのでidを指定しますが、ユーザーごとのパスワードは1つしかないのが普通なので、パスワードではidを指定しない、という状況です。
この場合以下のようなルーティングが考えられます。外側のusersは複数リソース、内側のpasswordは単数リソースです
resources :users do
resource :password
end
この場合は以下のルーティングが生成されます。
期待どおり、userにはidがあり、passwordにはidがありません。
なお、user_pathのようにそのリソースがパスの最後尾にある場合のidは「/:id」と表記されていますが、user_password_pathのようにuserがパスの途中にある場合では「/:users_id/」と表記されています。どちらもidです。
⚓ resourceベースでないルーティングの書き方
resourceベースでない、HTTPメソッドを指定したルーティングも可能です。その方がresourcesで書くよりもルーティングテーブルがシンプルになるのであれば、使う方がよいと思います。
get 'hello1', to: 'pages#hello'
get 'hello2', :controller => 'pages', :action => 'hello2'
get 'hello3/:id', to: 'pages#hello3'
post 'hello4', to: 'pages#hello4'
⚓ よくない書き方
以前は、GET・POST・PUT・UPDATE・DELETEのHTTPメソッドすべてにマッチさせたいときはmatchを使ったのだそうですが、ワイルドカードはセキュリティ上の隙になる可能性があるので、Rails 4からvia:オプションなしでのmatch指定は禁止されています。
match :hello5, to: 'pages#hello5' #禁止 (エラーが表示される)
match :hello5, to: 'pages#hello5', via: [:get, :post] #許される
さらに、かつては以下のように書くことができたのだそうです。
match ':controller(/:action(/:id))(.:format)'
こうすると「コントローラ」「アクション」「id」が実在してさえいればupdateやdestoryなど何にでもマッチしてしまうという、楽ちんかつ風通しの良すぎるルーティングになります。このヒューヒューの全通しルーティングは当時から危険視されていたらしく、チュートリアルや実験用以外で使うべきでないとされていたようですが、現在は完全に禁止されています。
⚓ namespace
Railsのルーティングでは以下のようにnamespaceを指定してパスをグループ化することができます。これを使用して、たとえば管理用ページ(admin)のパスや置き場所を仕切ることができます。
namespace :page do
get :privacy_policy
get :company_information
get :term_of_use
get :businessdeal
end
上のように、「名前付きルート」「パス」「コントローラ#アクション」にpageが追加されました。
上は素朴なgetメソッドルーティングでしたが、resourcesルーティングに名前空間を与えることもできます。
namespace :admin do
resources :users
end
同じく、「名前付きルート」「パス」「コントローラ#アクション」にadminが追加されました。
⚓ :moduleによる名前空間
:moduleオプションを使用してresourcesに名前空間を与えることもできますが、これは上と少し動作が異なります。
resources :users, module: :admin
#以下も同等
scope module: :admin do
resources :users
end
こちらは「コントローラ#アクション」にしかadmin名前空間が追加されていません。ここからわかるように、パスには表したくないが別のディレクトリにまとめたいコントローラがある場合に利用できます。
⚓ collectionとmember
既に見たように、resourcesを使用すれば主要な7つのルーティングが自動的に追加されますが、 それ以外のルーティングをそのリソースに追加したい場合はmemberまたはcollectionを使います。
さっきの「複数形はidなし、単数形はidあり」と同じ考え方で、「collection(集合)はidなし、member(個別)はidあり」と覚えましょう。以下のルーティングを例に取ります。
resources :books do
collection do
post :search
post :remove_multi
end
member do
get :thumbnail
get :sample_file
end
end
これをルーティングテーブルにすると以下のようになります。
見てのとおり、いつもの7つのルーティングに加えて4つのルーティングが追加されています。そしてcollectionで指定した2つにはidはなく、memberで指定した2つにはidがあります。
なお、この場合の名前付きルートはbooks_search_pathとかではなくsearch_books_pathのように上位のリソースが後ろに置かれていることにご注意ください。一応名前付きルートも確認してみましょう。
以下のような簡略版表記も使用できます。
resources :books do
post :search, on: :collection
get :thumbnail, on: :member
end
ここで1つ注意があります。以下のようにcollectionもmemberも指定せずに書いた場合はデフォルトで「member扱い」となります。
resources :books do
post :search
post :remove_multi
end
上の2つのリソースのルーティングテーブルを見てみると、確かにidが含まれており、member扱いされていることがわかります。
⚓ root
今更ですが、rootへのルーティングの書き方は以下のとおりです。これだけ他の書き方と比べて少し浮いている感じですね。
root to: 'page#top'
⚓ ルーティングのオプション
最後に、ルーティングでよく使われるオプションを紹介します。
⚓ only:とexcept:
resourcesでonly:またはexcept:オプションを使用することで、主要な7つのアクション(index, show, new, create, edit, update, destroy)を限定することができます。
# indexとshowアクションだけ使う場合
resources :prefectures, only: [:index, :show]
# destory アクション以外を使う場合
resources :prefectures, except: :destroy
updateやdestroyのような破壊的なアクションは事前にルーティングレベルで塞いでおきましょう。
⚓ リソース名の変更
asオプションを使用して、リソース名を変更することができます。
get 'home', controller: :users, as: 'user_root'
⚓ httpsの指定
以下のようにprotocol: httpsを指定することができます。
scope protocol: 'https://', constrains: {protocol: 'https'} do
root to: 'page#top'
end
なお、アプリの一部だけをHTTPS化するのは手間がかかる上にセキュリティ上の懸念も残るので、アプリ全体をHTTPS化することをおすすめします。
⚓ idを拡張
たとえば、以下のようにidの制約を変更してアルファベットのidを使用することができます。
resources :prefectures, id: '/^[a-z]+$/'
⚓ 最後に
Railsには他にも強力なルーティングのオプションがたくさんありますが、アドホックなカスタムルーティングを避け、なるべくresoucesやresource、onlyやexceptで素直かつ統一のとれたルーティングを生成するようにします。
コントローラが数百にのぼる巨大なルーティングをすべてresourcesとresourceで書いた例もあります。
ただし、そこでRESTfulにしようと頑張り過ぎないのも大事です。
⚓ 追伸
Rails 6.1では、ルーティングの記述を間違えたときにdid you mean?で推測する機能が加わります。
- PR: Add did you mean ssupport to UrlGeneration errors by tenderlove · Pull Request #39240 · rails/rails
⚓ 参考
- Railsガイド: Railのルーティング
- Rails 3 routes.rbまとめ
















