RailsアプリでBackbone.jsを使う

#Backbone.js Advent Calendar 5日目
Backbone.jsの本家ドキュメントにtodoリストをブラウザのLocalStorageを使って保存するチュートリアルがあります。

- [todo.js](http://documentcloud.github.com/backbone/docs/todos.html "todo.js")

今回は、このtodoアプリのバックエンドとして、Ruby on Railsを使うように変更してみたいと思います。

##Backbone.jsとサーバの通信

Backbone.jsはModelやCollectionの内容をサーバと同期するための手段を提供してくれています。標準で用意されているBackbone.syncはサーバがRESTfulと呼ばれるインタフェースを提供していることを前提に動作しますが、同期する方法を自作することも可能で、例えば上記のtodoリストのチュートリアルではLocalStorageにデータを保存するために、Storeという名前のオブジェクトを自作して用いています。そのため、Backbone.jsとサーバを同期する際は大きく分けて2通りの方法があることが分かります。

1.サーバにRESTfulなインタフェースを作り、標準のBackbone.syncを使う
2.独自のインタフェースを作り、独自の同期方法を実装する

Railsではscaffoldという機能を使うことで極めて簡単にRESTfulなAPIを作ることができるので、今回はこの機能を使ってみることにしましょう。

##Railsの用意

```sh
$ rails new todos
$ cd todos
$ rails g scaffold todo text:string done:boolean
$ rake db:migrate
```

あとはunderscore.jsとbackbone.jsをapp/assets/javascriptsに追加し、underscore.jsがbackbone.jsより先に読み込まれるように、app/assets/javascripts/application.jsを以下のように変えます。

```js
//= require jquery
//= require underscore
//= require backbone
//= require todos
```

Rubyを一行も書いていませんが、なんとこれで終わりです！あとはhtmlとjavascriptをがりがりと書いていくだけです

##htmlとjavascriptを書く

[元のチュートリアルのhtml](http://documentcloud.github.com/backbone/examples/todos/index.html)のbodyタグ内をapp/views/todos/index.html.erbにコピペします。このチュートリアルでは新しくTodoを作った時にDOMを作るのに、underscore.jsのテンプレート機能（とjQuery)を利用していますが、erbのロジックととunderscore.jsが標準で用意しているロジックが競合するため、そのままだとRailsがunderscore.jsのテンプレート用のロジックをerbのものだと勘違いしてエラーとなってしまいます。そこでtodos.jsに

```js
_.templateSettings = {
  interpolate: /\{\{(.+?)\}\}/g,
  evaluate: /\{%(.+?)%\}/g,
  escape: /\{%-(.+?)%\}/g
};
```

と記述して標準のロジックを変更してしまうことにします。上記の記述では、Python用WebフレームワークであるDjangoのテンプレートエンジンに似た記法を採用しています。そしてこの新しいロジックに合うようにコピペしたhtml内の二つの`<script type="text/template">`タグの中身を以下のように変更します。

```html
<script type="text/template" id="item-template">
  <div class="todo {{ done ? 'done' : '' }}">
    <div class="display">
      <input class="check" type="checkbox" {{ done ? 'checked="checked"' : '' }} />
      <div class="todo-text"></div>
      <span class="todo-destroy"></span>
    </div>
    <div class="edit">
      <input class="todo-input" type="text" value="" />
    </div>
  </div>
</script>
<script type="text/template" id="stats-template">
  {% if (total) { %}
    <span class="todo-count">
      <span class="number">{{ remaining }}</span>
      <span class="word">{{ remaining == 1 ? 'item' : 'items' }}</span> left.
    </span>
  {% } %}
  {% if (done) { %}
    <span class="todo-clear">
      <a href="#">
        Clear <span class="number-done">{{ done }}</span>
        completed <span class="word-done">{{ done == 1 ? 'item' : 'items' }}</span>
      </a>
    </span>
  {% } %}
</script>
```

最後にチュートリアルのjsをtodos.jsにコピペし、`TodoList`コレクションのlocalStorageプロパティを削除し、代わりに`url`プロパティに`"/todos"`をセットします。次のようになるはずです

```js
window.TodoList = Backbone.Collection.extend({
  model: Todo,

  // localStorage: new Store("todos"), 削除

  url: "/todos",

  done: function () {
    ...
  },
  ...
});
```

これで完成です。Railsを起動してアクセスしたら、動作していることが確認できるはずです。詳しい動作説明などは元のチュートリアルを参照してください。
このように、Railsのscaffold機能を用いることで、簡単にBackbone.jsのバックエンドを用意することができます。

##おまけ

Backbone.jsはRESTfulなAPIを対象とすると書きましたが、ある程度のカスタマイズを行うことができます。

まず、APIのURLのカスタマイズを行うことができ、上記のように、ModelやCollectionのurlプロパティまたはメソッドやurlRootプロパティでカスタマイズすることができます。

そして、サーバから返されるjsonは初期状態では以下のような形式を期待されていますが

```js
[{text: "hoge", done: false}, {text: "bar", done: true}]
```

例えば、他に検索ヒット数やページャのページ数などを同時に返して、以下のような形式になる場合は

```js
{info: {results: 100, total_pages: 10, current_page:1},
todos: [{text: "hoge", done: false}, {text: "bar", done: true}]}
```

parseメソッドを用いてカスタマイズすることができます。上記の例の場合なら

```js
var TodoList = Backbone.Collection.extend({
  ...
  parse: function (response) {
    this.info = response.info;  // ここでヒット件数の処理なども記述できる
    return response.todos  // 返される値がモデルにセットされる。Collectionだから配列を返している
  }
});
```