こんにちは、hachi8833です。「Railsのルーティングを極める」の後編です。今回はRails 4.0.3 + Ruby 2.1.1の環境で動作確認しています(Railsのルーティングを極める(前編))。
Railsのルーティング(routes)を極めよう
2012/03
baba
resourcesとネスト
Railsのルーティング記法の基本は、resourcesとresourceです。また、Railsのルーティングにはネストを含む多くのオプションがあり、自由度が飛躍的に高まっています。
以下の2つのルーティングは、ネストしていない単純なresourcesルーティングです。prefectures、articlesいずれも、コントローラに合わせて複数形で書く点にご注意ください。
resources :prefectures
resources :articles
rake routes
してみると、prefecturesとarticlesそれぞれについてRESTfulかつ標準的なアクション(index/create/new/edit/show/update/destroy)を網羅したなルーティングが一気に生成されています。
ところで、せっかくなのでRails 4.0のルーティング表示機能でも見てみましょう。development環境でRailsを起動して/rails/info/routesを参照します。名前付きルートもHelper列にわかりやすく表示され、[Path]と[Url]をクリックすれば_pathと_urlを切り替えて表示するという細かい芸もやってくれています。
なお名前付きルートのうち、_pathはドメイン名から下のパス、_urlはhttp://などから始まるフルパスであることは前編でも説明いたしました。生成された名前付きルートをrails console
で確認するには、app.に続けて名前付きルートを入力してみます。
複数形にはidはなく、単数形にはidがある、と覚えておくとよいでしょう。コードで名前付きルートを使用することで、生々しいパスをコードで書かずに済みます。名前付きルートはカスタムで指定することもできますが、なるべくこのように標準的なものを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オプションを使用して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)を限定することができます。updateやdestroyのような破壊的なアクションはルーティングレベルで塞ぐようにしておきましょう。
# indexとshowアクションだけ使用する場合
resources :prefectures, only: [:index, :show]
# destory アクション以外を使用する場合
resources :prefectures, except: :destroy
リソース名の変更
as
オプションを使用して、リソース名を変更することができます。
get 'home', controller: :users, as: 'user_root'
httpsの指定
以下のようにhttpsを指定することができます。
scope protocol: 'https://', constrains: {protocol: 'https'} do
root to: 'page#top'
end
idを拡張
たとえば、以下のようにidの制約を変更してアルファベットのidを使用することができます。
resources :prefectures, id: '/^[a-z]+$/'
最後に
Railsには他にも強力なルーティングのオプションがたくさんありますが、アドホックなカスタムルーティングを避け、なるべくresouces/resourceとonly/exceptで素直かつ統一のとれたルーティングを生成するようにします。コントローラが数百にのぼる巨大なルーティングをすべてresources/resourceで書いた例もあります。
ただし、そこでRESTfulにしようと頑張り過ぎないのも大事です。
参考
- Railsガイド: Railのルーティング
- Rails 3 routes.rbまとめ