Railsのルーティングを極める (後編)

こんにちは、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)を網羅したなルーティングが一気に生成されています。

nonnested

ところで、せっかくなのでRails 4.0のルーティング表示機能でも見てみましょう。development環境でRailsを起動して/rails/info/routesを参照します。名前付きルートもHelper列にわかりやすく表示され、[Path]と[Url]をクリックすれば_pathと_urlを切り替えて表示するという細かい芸もやってくれています。

nonnested_4.0

なお名前付きルートのうち、_pathはドメイン名から下のパス、_urlはhttp://などから始まるフルパスであることは前編でも説明いたしました。生成された名前付きルートをrails consoleで確認するには、app.に続けて名前付きルートを入力してみます。

namedroutes

複数形にはidはなく、単数形にはidがある、と覚えておくとよいでしょう。コードで名前付きルートを使用することで、生々しいパスをコードで書かずに済みます。名前付きルートはカスタムで指定することもできますが、なるべくこのように標準的なものをRailsに生成させる方が楽ですし混乱せずに済みます。

では、このうちprefecturesの下にcitiesとcompaniesをネストさせてみましょう。

resources :prefectures do
  resources :cities do
    resources :companies
  end
end

このときのルーティングテーブルは以下のようになります。
nested

見てのとおり、prefecturesのルーティングに加え、prefectures/cities、そしてprefectures/city/companiesという階層が追加されました。いずれも複数形の「resources」を指定しているので、prefectures, city, companyにはidがあります。

このときの名前付きルートは次のようになります。idの部分には適当な数字を入れてあります。
nestedroutes2

単数リソース

上では複数形のresourcesを使用してid付きのルーティングを生成しましたが、単純なページへのルーティングのようにidが不要な場合は単数形のresourceを指定することができます。

仮にprefectureでidが不要だとすると、以下のように単数形のresourceを指定し、prefectureも単数で書きます。

resource :prefecture

このときのルーティングは以下のようになります。

resource

見てのとおり、Pathにid:が含まれなくなり、indexアクションもなくなりました。なお、この記述「resource」も「prefecture」も単数ですが、これによって指定されるコントローラは「prefectures」と複数形になっていることにご注意ください。御存知のとおり、Railsではコントローラ名を複数形で書くことになっています。

名前付きルートを確認します。なお、無効なはずのidをわざと付けてみると、妙なパスが生成されました。
singleresource

複数リソースと単数リソースのネスト

今度は複数リソースと単数リソースの組み合わせの例を示します。ユーザーは複数いるのが普通なのでidを指定しますが、ユーザーごとのパスワードは1つしかないのが普通なので、パスワードではidを指定しない、という状況です。

この場合以下のようなルーティングが考えられます。外側のusersは複数リソース、内側のpasswordは単数リソースです

resources :users do
  resource :password
end

この場合は以下のルーティングが生成されます。

combinednest

期待どおり、userにはidがあり、passwordにはidがありません。
なお、user_pathのようにそのリソースがパスの最後尾にある場合にidは「/:id」と表記されていますが、user_password_pathのようにuserがパスの途中にある場合では「/:users_id/」と表記されています。どちらもidです。

nestedsingle

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

namespaces1

上のように、名前付きルートとパスとコントローラ#アクションにpageが追加されました。

上は素朴なgetメソッドルーティングでしたが、resourcesルーティングに名前空間を与えることもできます。

namespace :admin do
  resources :users
end

namespaces2

同じく、名前付きルートとパスとコントローラ#アクションにadminが追加されました。

:moduleオプションを使用してresourcesに名前空間を与えることもできますが、これは上と少し動作が異なります。

resources :users, module: :admin

#以下も同等
scope module: :admin do
  resources :users
end

namespace3

こちらはコントローラ#アクションにしか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

これをルーティングテーブルにすると以下のようになります。

collectionmember

見てのとおり、いつもの7つのルーティングに加えて4つのルーティングが追加されています。そしてcollectionで指定した2つにはidはなく、memberで指定した2つにはidがあります。

なお、名前付きルートはこの場合books_search_pathとかではなくsearch_books_pathのように上位のリソースが後になっていることにご注意ください。一応名前付きルートも確認してみましょう。

collectmember

以下のような簡略版表記も使用できます。

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扱いされていることがわかります。

nocollectmember

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'

asoption

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にしようと頑張り過ぎないのも大事です。

参考

関連記事

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

baba

ゆとりプログラマー。 高校時代から趣味でプログラミングを初め、そのままコードを書き続けて現在に至る。慶應義塾大学環境情報学部(SFC)卒業。BPS設立初期に在学中から参加している最古参メンバーの一人。Ruby on Rails、PHP、Androidアプリ、Windows/Macアプリ、超縦書の開発などを気まぐれにやる。軽度の資格マニアで、情報処理技術者試験(16区分17回 + 情報処理安全確保支援士試験)、技術士(情報工学部門)、Ruby Programmer Gold、AWSソリューションアーキテクト(アソシエイト)、日商簿記2級、漢検準1級などを保有。

babaの書いた記事

BPSアドベントカレンダー

週刊Railsウォッチ