はじめに
今回は、ルーティングについて詳しく解説します。
いまは、ルーティングはURLとアクションを結びつけること、というざっくりした理解だと思います。
実際ほとんどそのとおりで、決して難しいものではありません。
…
しかし、これからアプリケーションを開発するにあたっては、ルーティングには気を遣わなければなりません。
もし好き勝手なURLを一見無関係なアクションに結びつけていたらどうなるでしょう?
https://geektwitter.com/happy
というURLへのアクセスが、welcome#goldgym
アクションを実行するとしたら?
法則のないルーティングをすると、ユーザーはもちろん、開発者自身が一番困ることになります。
そこにアクセスすると何が起こるか、予測がつかないのです。
https://geektwitter.com/happy
は、世界平和をスローガンに掲げるGeekTwitter社の新サービスである「見るだけで脳に作用し幸せになるページ」を表示するかもしれません。
どのURLとアクションを結びつけたか全部覚えているからいいよ、と思う人もいるでしょう。
驚異の記憶力をもつあなたならそれでも問題ないでしょう。
しかしそのコードを他の人に見せるとき、はたしてすんなり理解してもらえるでしょうか?
ちなみに本家ツイッターでは、https://twitter.com/happy が有効なURLです。これはIDがhappy
のユーザーのページを表示するアクションに対応しています。このユーザーは、執筆時点で凍結されていました。
…
幸い、ルーティングにはこうするとわかりやすく書けるという作法があります。
さらに、Railsはわかりやすい書き方が簡単にできるようサポートしてくれます。
これからその考え方を学びましょう。
概要
まず、Railsにおけるルーティングについて概観します。
次にリソース指向という概念について解説しながら、それをRailsで実現するしくみについて解説します。
ルーティング - config/routes.rb
URLとアクションの対応づけをルーティングといいました。
Railsでは、ルーティングは以下のようにconfig/routes.rb
というファイルに記述して設定します。
Rails.application.routes.draw do
get 'tweets' => 'tweets#index'
post 'tweets' => 'tweets#create'
get 'tweets/new' => 'tweets#new'
get 'tweets/:id/edit' => 'tweets#edit'
get 'tweets/:id' => 'tweets#show'
patch 'tweets/:id' => 'tweets#update'
put 'tweets/:id' => 'tweets#update'
delete 'tweets/:id' => 'tweets#destroy'
end
8つのURLがそれぞれのアクションに結びついているのがなんとなくわかるでしょう。
あとで解説しますが、これらはアプリケーションによく使われるルーティングです。
これを見るとふたつ気になる点があります。
まず、ルーティングはURLとアクションを結びつけるものだと言ったのに、URLがなんだか短いということです。
もう一つは、get
とかpost
とか、よくわからないのが左にくっついていることです。
順番に解説します。
短いURL - パス
これはURLではなくパスと呼ばれるものです。
ここでTwitterのURLをいくつか見てみます。
https://twitter.com/
https://twitter.com/i/notifications
https://twitter.com/i/moments
https://twitter.com/Twitter
全てにおいて https://twitter.com/ という部分が共通していますね。これはアプリケーション固有のものなので、同一のアプリケーション内で変わることはありません。
…
この共通部分は、開発時の環境とリリース後の本番環境で変わります(環境とは、アプリケーションを動かす場所のことです)。前者は自分のパソコンの中であり、後者はもっとたくさんのアクセスに耐えられる別のコンピュータです。
もしURLをすべて書いていたら、別の環境に移行するときにファイルを書き換えなければならないので、config/route.rb
にはパスだけを記述しています。
全てのアクションに共通するパスをルートパスといい、/
と表現されます。ルートとは英語でroot、つまり根であり、全てのパスの根本にあるという意味を持ちます(注: まぎらわしいのですが、ルーティングは英語でrouting、すなわち道をあらわすrouteであり、rootとは関係ありません)。
あらゆるパスは、ルートパスの下に連なるので、/tweets
のように書けます。
というわけで、config/routes.rb
は、URLではなく、パスとアクションを結びつけています。
Rails.application.routes.draw do
get '/tweets' => 'tweets#index'
# 省略
end
getやpostなど - HTTPメソッド
HTTPメソッドと呼ばれるものです。
HTTPメソッドは好き勝手に設定できるものではありませんが、数種類しかないので覚えるのは難しくないでしょう。
実はアクションへのルーティングは、URLだけではなく、URLとHTTPメソッドの組み合わせによって決定されます。上の例でも、同じパスに対して2つのルーティングが設定されていますが、互いにHTTPメソッドが異なるために、別のアクションに結びつけることができます。
HTTPメソッドは、GETやPOSTのように大文字で表記されます。
config/routes.rb
では小文字で書かなければなりません。
リソース指向
このように、ユーザーのやりたいことを主語(リソース)と述語(HTTPメソッド)の組み合わせで表現しようという考え方を、リソース指向といいます。
**リソースとは、ユーザーが操作する対象(情報)すべてです。**猫も杓子もリソースです。
操作する対象は、データベースのテーブルであることもありますし、ある特定のレコードである場合もあります。データベースではなく、表示するページそのものを指すこともあります。
とにかく操作可能な情報すべてをリソースと言うのです。
以下にはじめに紹介したルーティングを表にまとめました。
これはリソース指向なルーティングになっています。
なお、リソースはパスで表現します。リソースは情報そのものであり、パスはリソースを指し示す名前というイメージです。
HTTPメソッド | パス(リソース) | アクション | やりたいこと |
---|---|---|---|
GET | /tweets | tweets#index | ツイート一覧のページを表示 |
POST | /tweets | tweets#create | ツイートを投稿 |
GET | /tweets/new | tweets#new | ツイートを新規作成するページを表示 |
GET | /tweets/:id/edit | tweets#edit | ある一つのツイートの編集ページを表示 |
GET | /tweets/:id | tweets#show | ある一つのツイートのページを表示 |
PATCH | /tweets/:id | tweets#update | ある一つのツイートを更新 |
PUT | /tweets/:id | tweets#update | ある一つのツイートを置換 |
DELETE | /tweets/:id | tweets#destroy | ある一つのツイートを削除 |
tweets#update
アクションを実行するルーティングが2つ設定されています。これは見ての通りPATCHメソッドとPUTメソッドの違いによるのですが、ここでは投稿済みのツイートを更新するものということでほとんど同じと考えて結構です。
…
「ツイート一覧のページを表示」は、「/tweets
が表すリソースをGET
すること」
「ツイートの投稿」は、「/tweets
が表すリソースにPOST
すること」
「ツイートの削除」は、「/tweets/:id
が表すリソースをDELETE
すること」
この考え方を身に着けていきましょう。
リソース指向なルーティングの実践
リソース指向は、単なる思想であり、作法です。
作法には、必ずこのように書かなければ動かないといった強制力はありません。しかし、多くのRailsエンジニアがこの作法を守って名前を決定しています。
誰もがリソース指向に基づいて名前をつけるなら、誰が書いたコードでもパスとHTTPメソッドを見るだけで、なんとなく何をするアクションに結びついているのかがわかるのです。
以下に、Railsでリソース指向なルーティングを実装するにあたってのルールやテクニックをまとめました。
- データベース上のリソースは
/(テーブル名)/(+α)
というパスで表現する - ページを表示するアクションはすべてGETメソッドで呼び出す
- CRUD操作は
resources
を用いて書く - アクション名は
resources
に従う -
resources
で定義するルーティングを限定する
順番に見ていきましょう。
① データベース上のリソースは/(テーブル名)/(+α)
というパスで表現する
繰り返しになりますが、リソースはユーザーが操作する対象(情報)すべてです。
例えば、/tweets
というリソースはtweets
テーブルを表し、tweets/:id
はtweets
テーブルの特定のレコードを意味します。
…
特定のツイートを指すための/tweets/:id
というパスは一体何者でしょうか。
ツイートは、tweets
テーブル内のid
カラムの値で識別されます。id
が1のツイートは1つしかありません。
これを利用して、あるひとつのツイートを指すパスは、/tweets/1
のようにid
を埋め込むことで書けそうです。
id
の数字が入っている部分は、当然id
によって変わってしまいます。tweets
テーブルのレコードの数はどんどん変化するので、get '/tweet/1' => 'tweet#show'
などとひとつひとつルーティングを設定するわけにもいきません。
そこで、get '/tweets/:id' => 'tweets#show'
のようにルーティングを指定します。
これにより、/tweets/○○
というパスで、○○の中に何が入っていようとそれをid
として認識するようになります。
パスに書かれたid
の値は、対応するアクションの中でparams
という仕組みを使って受け取ることができます。
具体的な実装は、投稿機能の教材で解説します。
② ページを表示するアクションはすべてGETメソッドで呼び出す - リダイレクト
さきほどの表で赤色で示した部分を見てください。
GETメソッドに対応付けられたアクションの役割は、すべて「ページの表示」でした。
一方、GET以外のHTTPメソッドの名前には、それぞれのアクションの内容が表現されています。POSTやDELETEなどです。
これらのメソッドが結び付けられたアクションは、リソースの操作にのみ責任を負っています。
例えば、/tweets
というリソースにPOST
メソッドでアクセスした場合を考えましょう。これはツイートを新しく投稿することを意味し、先の表ではtweets#create
アクションに結び付けられています。
tweets#create
アクションは、文字通りツイートを投稿することだけを担当します。「リソースを投稿すること」というのがユーザーのやりたいことだからです。
とはいえアクションを実行したらその結果のページを表示しなければなりません。
ツイートが投稿された(あるいは投稿できなかった)ことをユーザーに知らせるためです。
そこで、GET以外のメソッドに対応付けられたアクションは、他のアクションにリダイレクトするという方法でページを表示します。
…
リダイレクトは、現在のアクションに続けて別のアクションを実行するための仕組みです。
下図の赤矢印で、tweet#create
アクションは自分の役目を終え、ページの表示をtweet#index
アクションに委ねています。
tweets#create
アクションは**tweets
テーブルにレコードを追加するまでの責任を負い、
tweets#index
アクションはtweets
テーブルから全てのレコードを取得して一覧ページを表示するまで**の責任を負います。
「新しいツイートを投稿する」というユーザーのやりたいことは、この2つのアクションが続けて実行されることで実現されていたのです。
ツイートの一覧画面は、例えばツイートを削除した後にも表示したいでしょう。その場合にも、tweets#destroy
アクションからtweets#index
アクションへリダイレクトします。
リダイレクトを利用することで、リソースに対する操作だけを明確に切り分けて書くことができます。
また、「tweets
テーブルからツイートのレコードを取り出して表示する」という一連の処理を何度も書かずに済みます。必要なタイミングで、tweets#index
アクションへリダイレクトするということを書くだけです。
これも具体的には、投稿機能の教材で解説します。
③ CRUD操作はresources
を用いて書く
ここまで読んで、なんだか複雑な感じがしたかもしれません。
細かいことは抜きにして、簡単に作法に従った書き方をする方法はないものでしょうか?あります。
実は、最初に8行も書いたルーティングは、以下の1行に置き換えられます。
Rails.application.routes.draw do
resources :tweets
end
resources
、リソースの複数形です。
よくよく考えてみれば、ひとつのテーブルに対する操作は、そんなに種類が無いような気がします。レコードの作成、読み込み、更新、削除などです。
そう、先に挙げた8つのルーティングは、テーブルを操作するアクションのほとんどを網羅しているのです!
resources
のあとに:tweets
のようにコントローラー名を書くことによって、そのテーブルやレコードをリソースとする主要な8つのルーティングを定義することができます。
このリソースに対する基本的な操作を、作成(Create)、読み込み(Read)、更新(Update)、削除(Delete)の頭文字を取ってCRUDと言います。
アプリケーションはデータベースが主人公と言っても過言ではありませんし、そのテーブルに対するCRUDアクションのルーティングが一気に設定できるとあれば、これを使わない手はありません。
さらに、resources
を用いることでエンジニアによるブレの生じない命名になり、良いことづくめです。
…
一方resources
に含まれず、個別にルーティングを設定する必要があるアクションとしては、例えば検索機能のアクションが挙げられます。tweets#search
アクションなどを定義する必要があります。
他にも、https://geektwitter.com/happy
のようなコマーシャルページや、ログインしていないユーザー向けのトップページなどの、データベースの操作を伴わない静的なページを表示するだけのアクションも、独自にルーティングを設定する必要があります。
④ アクション名はresources
に従う
リソース指向とは直接関係のない話ですが、アクション名についても説明しておきます。
上に見たように、resources
はテーブルやレコードをリソースとする主要な8つのルーティングを追加してくれます。
ルーティングと同時に、アクション名も決まっています。tweet#index
やtweet#create
などです。
resources
を使ってルーティングを行えばこれらのアクション名に従わざるを得ません。
しかしよく理解しないうちは、ツイートの一覧ページを表示するアクションをtweets#top
アクションに書いてしまう人も見られます。
そのため、自分でget '/tweets' => 'tweets#top'
のようなルーティングを設定しなければなりません。
あるテーブルのレコードの一覧表示はindex
アクションで行うのが通例であり、top
のような別の名前で定義すると、他の人が見たときに余計な混乱を招きます。
resources
で定義できるアクションを独自の名前で定義しないように注意しましょう。
⑤ resources
で定義するルーティングを限定する
resources
は便利なのですが、8つもルーティングを実装したくない場合もあります。
例えば、ツイートの編集機能は実装したくない!という場合、tweets#edit
アクションもtweets#update
アクションも必要ありません。
resources
は、only`オプションを使うことで、定義するルーティングを限定できます。
以下は、編集機能を実装しない場合の例です。
Rails.application.routes.draw do
resources :tweets, only: [:index, :create, :new, :show, :destroy]
end
#問題!〜ルーティングエラーを直そう〜
resourcesを設定すると今までのパスが変わってしまう部分が出てきてこのままではエラーが起こってしまうので適切なページを直してみましょう!
#ヒント
RoutingErrorが出た際にコマンドプロンプトまたはターミナルで$ rails routes
と打ちましょう!
new_tweet GET /tweets/new(.:format) tweets#new
ここの部分のパスを見直してみよう!
#答え
ビュー - index.html.erb
<h1>GeekTwitter</h1>
<h3>Tweet一覧</h3>
<%= link_to "新規投稿へ", new_tweet_path %>
<div class="twes-container">
<% @burgers.each do |t| %>
<div class="tweet">
<%= t.body %>
<%= t.created_at %>
</div>
<% end %>
</div>
<%= link_to "新規投稿へ", tweets_new_path %>
を
<%= link_to "新規投稿へ", new_tweet_path %>
に直す!!
意外と簡単にエラーを解決できましたね!RoutingErrorが出たら$ rails routes
を使ってパスを見直してみましょう!
おわりに
以上、ルーティングについての解説でした。
細かい話も多かったので、いっぺんに身につけるのは難しいかもしれません。必要に応じて読み返すようにしてください。
…
あなたがどんなパスをどんなアクションに結びつけようと、そのルーティングを妨げる人もプログラムもありません。
しかし、適当な命名をしていては誰も触れたくないわかりにくいアプリケーションができていくでしょう。
だからこそ今回解説した作法をよく学び、良いルーティングをすることを心がけなければなりません。