RailsにおけるRESTfulなURL設計勉強会 千駄ヶ谷.rb #12 #sendagayarb にて発表予定の記事です。
この記事は、qiitaのRESTful APIとしてのRailsとクライアントとしてのJavaScriptにも掲載しています。
よくあるAjaxを利用したページの例
Sedndagaya.rbというグループへ投稿出来るサイトがあるとします。
この時、グループのページにアクセスすると、グループの詳細情報と、グループへ投稿された記事が表示されます。
このとき、大きく分けて3つの描写が行われます。
- 通常のリクエストに対するレスポンスの描写
- 非同期のリクエストに対するレスポンスの描写
- DOM要素のイベントに対するレスポンスの描写
その時の流れは以下のようになります。
- エンドユーザーがあるURIをブラウザ経由でアクセスする。 たとえば、
http://example.com/groups/1
にHTTP GETリクエストを送信してアクセスする。 - Webサーバー(アプリケーション・サーバー)が対応するHTMLを返す。この例の場合、
/groups/1
に対応するリソースを返す。 - ブラウザがHTMLをレンダリングする。これが1つ目の通常のリクエストに対するレスポンスの描写となる。
- この時、グループに投稿された記事を非同期に取得する。非同期に取得した記事は以下のように、インジケーターが表示されている部分へ差し込まれる。
- パスが
/groups/1
の場合、特定のJavaScript
が実行されるようにしておく。 - その
JavaScript
が/groups/1
に関連するリソースを非同期のHTTP GETリクエストを送信し、その結果をHTML内の特定のDOM要素へ差し込む。これが2つ目の非同期のHTTP GETリクエストに対するレスポンスの描写 - 他にもレンダリングされたHTMLに含まれるUIにイベントを付与する。
- 例えば、
[このグループに投稿する]
というボタンをクリックすると以下のように投稿フォームを表示する。これが3つ目のDOM要素のイベントに対するレスポンスの描写
更に?
先の例では、GETリクエストによるリソースの取得のみを行いました。更に、以下のようにグループへ記事を投稿する処理も考えてみましょう。
[投稿する]
というボタンをクリックするとグループに紐付いた投稿を作成する。/group/1/posts
へHTTP POSTリクエストを送信する- リクエスト終了後は複数の処理が
javascript
により実行される。- フォームを閉じる
- 投稿のフィードバックを表示する
- 作成された記事をHTMLに挿入する
必要なHTTPメソッドと対応するURIを洗い出してみる
RESTfulに作っておくと、backbone.js
など標準的な JavaScriptフレームワーク
と連携しやすいです。
例えば以下のURIはRESTfulの指向で考えると統一されたインターフェースから導けば良いので、簡単に定義可能です。ここで言う統一されたインターフェースとはRailsにおけるRESTfulです。
- GET
http://example.com/groups/1
- GET
http://example.com/groups/1/posts
- POST
http://example.com/groups/1/post
- GET
http://example.com/groups/new
JavaScriptの重要性
このように、通常のHTTP GETリクエストの処理と合わせた非同期処理を行う際に JavaScript
は必須となります。
このJavaScript
をどこにどのように書くかが大事です。
JavaScriptの実行Routing
先の例のように通常のHTTPリクエストの後に、
- 非同期のHTTPリクエストを行いたい
- 特定のDOM要素のイベントをハンドリングしたい
といった要件は、通常URIごとに異なります。
その時に、現在ブラウザが指し示しているURIでは関係ないJavaScript
が全て同じ所に記載されているのはメンテナスがしづらいです。
$(function() {
// user pageで使います
$('.user a.follow').click(function(e) {
e.preventDefault();
// ajaxでフォロー処理をします
});
// イベントの検索ページで使います。
$('form.event_search input[type="text"]').click(function(e) {
e.preventDefault();
// 検索時の処理
});
// グループページで使います。
$('.group a.new_post').click(function(e) {
e.preventDefault();
// グループに投稿する記事フォームを表示
}):
// … とにかく続く
// ウゲェ
- たまたま
JavaScript
の制御に利用するclass名やid名を追加したり削除したりすると悲惨 - そのページに関係ある処理とそうでない処理が分かりづらい
たまたまJavaScript
の制御に利用するclass名やid名を追加したり削除したりすると悲惨
デザイナーさんとの協業時に発生しそうです。これは、デザイナーさんとclass名、id名などのルール決めをするか、JavaScript
側では HTML5
の data-xxxx
attributeを利用することで回避できそうです。HTML内にdata-xxxx
が溢れることになります。
<form data-name="event_search">
<!-- 〜〜〜 -->
</form>
$('form[data-name="event_search"] input[type="text"]').click(function(e) {
e.preventDefault();
// 検索時の処理
});
そのページに関係ある処理とそうでない処理が分かりづらい
複数ページの window.onload をまとめて書ける jQuery プラグインを書きましたという記事が良いです。この中に出てくるjQuery-Routerの例を示します。
$.route(
{
path: /\/users/,
func: function() {
$('.user a.follow').click(function(e) {
e.preventDefault();
// ajaxでフォロー処理をします
});
}
},
{
path: /\/events/,
func: function() {
$('form.event_search input[type="text"]').click(function(e) {
e.preventDefault();
// 検索時の処理
});
}
},
{
path: /\/groups/,
func: function() {
$('.group a.new_post').click(function(e) {
e.preventDefault();
});
}
},
);
どのページでどの javascript
が実行されるかが分かりやすくなりました。ネストが深くなるので、Classで分けるなどの工夫をすると良いですね。
なお、手前味噌ですがrailsで言うパス内の /groups/:id
といった params
を参照できるようにしたバージョンは、jQuery-Routerからどうぞ。
*.js.erb
例えば、/groups/:id/posts
への非同期のHTTP GETアクセス時にの JavaScript
処理を、app/views/posts.js.erb
に直接書く方法もあります。
この方法は、JavaScript
の記述がapp/assets/javascripts/*
と app/views/*.js.erb
に散らばり見通しが悪くなるので、あまりおすすめしません。
JavaScriptのMVCの利用
backbone.js
spine.js
などが有名どころです。
backbone.js
- model: resourceの操作をRESTfulに管理する
- collection: resourcesの操作をRESTfulに管理する
- router: どのURIで何をするかを管理する
- view: どのDOM要素で何をするかを管理する
詳しくは、qiitaのbackbone.jsでの検索結果を確認しましょう。
まとめ
このように通常のHTTPリクエストと、非同期のHTTPリクエストの組み合わせのページを作ることが多い昨今、RESTful APIとしてのRailsとクライアントとしてのJavaScriptとの関係をどう記載するのかが重要となってきています。
その際、通常のHTTPリクエストと非同期HTTPリクエストを同じ統一インターフェースであるRESTfulな設計で管理すると一貫性が出て開発効率の向上につながると考えています。