RESTful APIとしてのRailsとクライアントとしてのJavaScript

RailsにおけるRESTfulなURL設計勉強会 千駄ヶ谷.rb #12 #sendagayarb にて発表予定の記事です。

この記事は、qiitaのRESTful APIとしてのRailsとクライアントとしてのJavaScriptにも掲載しています。

よくあるAjaxを利用したページの例

Sedndagaya.rbというグループへ投稿出来るサイトがあるとします。
この時、グループのページにアクセスすると、グループの詳細情報と、グループへ投稿された記事が表示されます。

このとき、大きく分けて3つの描写が行われます。

  • 通常のリクエストに対するレスポンスの描写
  • 非同期のリクエストに対するレスポンスの描写
  • DOM要素のイベントに対するレスポンスの描写

その時の流れは以下のようになります。

  1. エンドユーザーがあるURIをブラウザ経由でアクセスする。 たとえば、 http://example.com/groups/1 にHTTP GETリクエストを送信してアクセスする。
  2. Webサーバー(アプリケーション・サーバー)が対応するHTMLを返す。この例の場合、 /groups/1 に対応するリソースを返す。
  3. ブラウザがHTMLをレンダリングする。これが1つ目の通常のリクエストに対するレスポンスの描写となる。 group0
  4. この時、グループに投稿された記事を非同期に取得する。非同期に取得した記事は以下のように、インジケーターが表示されている部分へ差し込まれる。 group0
  5. パスが /groups/1 の場合、特定の JavaScript が実行されるようにしておく。
  6. その JavaScript/groups/1 に関連するリソースを非同期のHTTP GETリクエストを送信し、その結果をHTML内の特定のDOM要素へ差し込む。これが2つ目の非同期のHTTP GETリクエストに対するレスポンスの描写 group0
  7. 他にもレンダリングされたHTMLに含まれるUIにイベントを付与する。 group0
  8. 例えば、 [このグループに投稿する] というボタンをクリックすると以下のように投稿フォームを表示する。これが3つ目のDOM要素のイベントに対するレスポンスの描写 group0

更に?

先の例では、GETリクエストによるリソースの取得のみを行いました。更に、以下のようにグループへ記事を投稿する処理も考えてみましょう。

group0

  1. [投稿する] というボタンをクリックするとグループに紐付いた投稿を作成する。
  2. /group/1/posts へHTTP POSTリクエストを送信する
  3. リクエスト終了後は複数の処理が javascriptにより実行される。
    • フォームを閉じる
    • 投稿のフィードバックを表示する
    • 作成された記事をHTMLに挿入する group0

必要な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が全て同じ所に記載されているのはメンテナスがしづらいです。

ひとつの.jsファイルに全部書く例
$(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な設計で管理すると一貫性が出て開発効率の向上につながると考えています。