更新情報
- 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まとめ