RailsアプリでBackbone.jsを使う（2015年版）

Ruby on RailsとBackbone.js始めました。

Rails × Backbone.jsを試したくて調べて出てきた記事「[RailsアプリでBackbone.jsを使う](http://qiita.com/yuku_t/items/c14d4554f6af8b4e6159)」を読み、まったく同じことをやろうとしたら[色々ハマりました](http://ohbarye.hatenablog.jp/entry/2015/08/15/233000)。
自分がRailsもBackbone.jsも初心者なために陥ったのが大部分だと思いますが、4年近い歳月の間に変わってしまったことも多くあったようです。

そこで、目指すところは元記事と同じまま手順を2015年版に書き換えてみたいと思います。

> Backbone.jsの本家ドキュメントにtodoリストをブラウザのLocalStorageを使って保存するチュートリアルがあります。
[todo.js](http://documentcloud.github.io/backbone/docs/todos.html)
今回は、このtodoアプリのバックエンドとして、Ruby on Railsを使うように変更してみたいと思います。

## Rails

`rails new`〜`rake db:migrate`まで。

```bash
$ rails new todos
$ cd todos
$ echo "gem 'rails-backbone'" >> Gemfile
$ bundle install
$ rails g backbone:install
$ rails g scaffold todo title:string done:boolean
$ rake db:migrate
```

元記事ではbackbone.jsと依存ライブラリをダウンロードして`app/assets/javascripts`に入れていましたが、今回は**rails-backbone**をgemとして追加することにします。

* gemとして追加してやったほうが管理が楽

gem名は**rails-backbone**ですがレポジトリ名はなぜか[backbone-rails](https://github.com/codebrew/backbone-rails)です。ご注意ください。

* 元記事と同じやり方だとAjax時にRailsのCSRF対策に引っかかってしまう

Backbone.syncをハックして避ける方法もあるようですが同gemでも回避できるようなので。

[[backbone][rails][CSRF][ajax]backbone syncでcsrfタグもpostする方法](http://threetreeslight.com/post/29754340086/backbonerailscsrfajaxbackbone)
[Backbone.jsでRailsのCSFR防止tokenをうまく扱う](http://qiita.com/yaotti/items/8fdef98329012255fa1d)


## View Template

> 元のチュートリアルのhtmlのbodyタグ内をapp/views/todos/index.html.erbにコピペします。

元記事のとおりhtmlをコピペしましょう。

scriptの読み込みは**application.js**の`require`で行うようになっているので以下の記述は削除します。

```html+erb:app/views/todos/index.html.erb
  <script src="spec/support/jquery.js"></script>
  <script src="spec/support/underscore.js"></script>
  <script src="spec/support/backbone.js"></script>
  <script src="backbone.localStorage.js"></script>
  <script src="todos.js"></script>
```

### Separation

サンプルのTodoアプリではBackbone.jsで使うテンプレートをindex.html.erbに`<script type="text/template">`として読み込んでいます。このままだとerbの制御文字とunderscore.jsが競合してparse errorになるので元記事ではunderscore.jsのロジックを書き換えています。

同じやり方でも構いませんがサーバサイドとクライアントサイドで使用するファイルは極力分けて管理したいので今回はテンプレートを外部ファイル化してみます。

まず、**index.html.erb**内のテンプレート記述2つ（以下）を削除します。

```html+erb:app/views/todos/index.html.erb
<!-- Templates -->

<script type="text/template" id="item-template">
  <div class="view">
    <input class="toggle" type="checkbox" <%= done ? 'checked="checked"' : '' %> />
    <label><%- title %></label>
    <a class="destroy"></a>
  </div>
  <input class="edit" type="text" value="<%- title %>" />
</script>

<script type="text/template" id="stats-template">
  <% if (done) { %>
    <a id="clear-completed">Clear <%= done %> completed <%= done == 1 ? 'item' : 'items' %></a>
  <% } %>
  <div class="todo-count"><b><%= remaining %></b> <%= remaining == 1 ? 'item' : 'items' %> left</div>
</script>
```

次に`app/assets/javascripts/backbone/template`に以下の2ファイルを追加しましょう。

```html+erb:app/assets/javascripts/backbone/template/item-template.jst.ejs
<div class="view">
  <input class="toggle" type="checkbox" <%= done ? 'checked="checked"' : '' %> />
  <label><%- title %></label>
  <a class="destroy"></a>
</div>
<input class="edit" type="text" value="<%- title %>" />
```

```html+erb:app/assets/javascripts/backbone/template/stats-template.jst.ejs
<% if (done) { %>
  <a id="clear-completed">Clear <%= done %> completed <%= done == 1 ? 'item' : 'items' %></a>
<% } %>
<div class="todo-count"><b><%= remaining %></b> <%= remaining == 1 ? 'item' : 'items' %> left</div>
```

これらはjavascriptからそれぞれ以下のような形式で呼び出せます。

* `JST['backbone/templates/item-template']`
* `JST['backbone/templates/stats-template']`

テンプレートの分離はこれで完了です。

## Backbone.js

`app/assets/javascripts/backbone/todos.js.coffee`を削除し、[本家のtodos.js](http://localtodos.com/todos.js)をコピーして配置します。

jstとして分離したテンプレートを使用するように変更します。

```js:app/assets/javascripts/backbone/todos.js
  // The DOM element for a todo item...
  var TodoView = Backbone.View.extend({

    // Cache the template function for a single item.
    // template: _.template($('#item-template').html()),
    template: JST['backbone/templates/item-template']

(中略)

  // Our overall **AppView** is the top-level piece of UI.
  var AppView = Backbone.View.extend({

    // Our template for the line of statistics at the bottom of the app.
    // statsTemplate: _.template($('#stats-template').html()),
    statsTemplate: JST['backbone/templates/stats-template']
```

これだけで動くようになりますが、Rails使っているのに今更JavaScriptって無いんだろうな…と思いCoffeeScript化してみました。長くなるのでGistに貼り付けておきました。

https://gist.github.com/ohbarye/83e506ca726087f2cc51

## Design

jsと同様にcssも本家からコピーしてきます。

cssを自前でscssにするのは…まァいいか、という気持ちです。変換をブラウザで行ってくれる便利サイト（[css2sass](http://css2sass.herokuapp.com/)）がありますので[本家のtodos.css](http://localtodos.com/todos.css)をコピーしてここでscssに変換し、デフォルトで作成されている`app/assets/stylesheets/todos.css.scss`に貼り付けましょう。

Todo削除アイコン**destroy.png**がそのままだと読み込めないので以下のように書き換えます。


```css:app/assets/stylesheets/todos.css
#todo-list .destroy {
	position: absolute;
	right: 5px;
	top: 20px;
	display: none;
	cursor: pointer;
	width: 20px;
	height: 20px;
/*	background: url(destroy.png) no-repeat; */
	background: url(//localtodos.com/destroy.png) no-repeat;
}
```

## Completion!

これにて完了です。`rails s`して動作確認しましょう。

Backbone.jsも引き続き学んでいくわけですが、このアプリを軸にMarionette.jsやらMongoDBやらも試していきたいと思っています。