Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連
  • 勉強会

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

更新情報

  • 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かつ標準的なアクション(indexcreateneweditshowupdatedestroy)を網羅したなルーティングが一気に生成されています。

nonnested

ところで、せっかくなのでRails 4.0以降で使えるルーティング表示機能でも見てみましょう。development環境でRailsを起動して、ブラウザでhttp://localhost:3000/rails/info/routesを開くと以下のように表示されます。

名前付きルートもHelper列にわかりやすく表示され、[Path]と[Url]をクリックすれば名前付きルートの*_path*_urlを切り替えて表示するという細かい芸もやってくれます。

nonnested_4.0

参考までに、最近のRailsでは以下のように表示が洗練されています(6.0.3.4で確認)。

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

namedroutes

「複数形にはidはなく、単数形にはidがある」と覚えておくとよいでしょう。

このようにコードで名前付きルートを使うことで、生のURLをコードに書かずに済みます。

名前付きルートはカスタムで指定することもできますが、なるべくこのように標準的なものを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'

よくない書き方

以前は、GETPOSTPUTUPDATEDELETEの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による名前空間

: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つ注意があります。以下のようにcollectionmemberも指定せずに書いた場合はデフォルトで「member扱い」となります。

resources :books do
  post :search
  post :remove_multi
end

上の2つのリソースのルーティングテーブルを見てみると、確かにidが含まれており、member扱いされていることがわかります。

nocollectmember

root

今更ですが、rootへのルーティングの書き方は以下のとおりです。これだけ他の書き方と比べて少し浮いている感じですね。

root to: 'page#top'

ルーティングのオプション

最後に、ルーティングでよく使われるオプションを紹介します。

only:except:

resourcesonly:またはexcept:オプションを使用することで、主要な7つのアクション(index, show, new, create, edit, update, destroy)を限定することができます。

# indexとshowアクションだけ使う場合
resources :prefectures, only: [:index, :show]
# destory アクション以外を使う場合
resources :prefectures, except: :destroy 

updatedestroyのような破壊的なアクションは事前にルーティングレベルで塞いでおきましょう。

リソース名の変更

asオプションを使用して、リソース名を変更することができます。

get 'home', controller: :users, as: 'user_root'

asoption

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には他にも強力なルーティングのオプションがたくさんありますが、アドホックなカスタムルーティングを避け、なるべくresoucesresourceonlyexceptで素直かつ統一のとれたルーティングを生成するようにします。

コントローラが数百にのぼる巨大なルーティングをすべてresourcesresourceで書いた例もあります。

ただし、そこでRESTfulにしようと頑張り過ぎないのも大事です。

追伸

Rails 6.1では、ルーティングの記述を間違えたときにdid you mean?で推測する機能が加わります。

参考

関連記事

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


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。