backbone-on-railsでモデル一覧を表示する。

既存のRailsプロジェクトにbackbone.jsを使ってみようと思い立って、backbone-on-railsを入れてみたのでメモ。

Gemfileに以下を追加する。

```ruby:Gemfile
gem 'backbone-on-rails'
```

以下を実行する。

```bash:bash
bundle install
rails generate backbone:install
```

そうすると、assets以下に以下のディレクトリができる。

* javascript/collections
* javascript/models
* javascript/routers
* javascript/views
* templates

templatesは、html置き場。
ecoというテンプレートエンジンを使って書ける。ecoはCoffeeScriptで記述するテンプレートだけれど、記述がほぼerbのように書けるので、erbで書いてきた人達にはストレスはないと思う。slimで書いている自分にはややストレス…。

試しにscaffoldでやってるベタなmodelに対してやってみる。
backboneのscaffoldを作成する。

```bash:bash
rails generate backbone:scaffoild hoges
```

最初はアラートが出るようになっているので、まずコメントアウトしておく。
rails_project.js.coffeeは各々のプロジェクト名になっているので注意。

```coffeescript:rails_project.js.coffee
window.RailsProject =
  Models: {}
  Collections: {}
  Views: {}
  Routers: {}
  initialize: ->
    # alert 'Hello from Backbone!'

$(document).ready ->
  RailsProject.initialize()
```

Hello World的なのは、他の記事を参考にしてください。よくあるから。

この記事では、次の順序でhogeリストを出すところまでやる。

1. collectionを定義する。
2. routerを定義する。
3. collectionを処理するviewを定義する。
4. 個別のmodelを処理するviewを定義する。
5. routerを起動してデータを取ってくる。


1つ目はcollectionを定義。

```coffeescript:assets/javascript/collections/hoges.js.coffee
class RailsProject.Collections.Hoges extends Backbone.Collection

  model: RailsProject.Models.Hoge
  url: 'hoges'
```

URLは、HogesクラスがHogeモデルのJSONを取りにいくパス。
fetchメソッドを呼ぶと、/hogesにAjaxでアクセスしてJSONを取ってくることを想定している。

2つ目に、routerを定義。

```coffeescript:assets/javascript/routers/hoges_router.js.coffee
class RailsProject.Router.Hoges extends Backbone.Router
  routes:
    '': 'index'

  initialize: ->
    @collection = new RailsProject.Collections.Hoges()
    @collection.fetch(reset: true)

  index: ->
    view = new RailsProject.Views.HogesIndex(collection: @collection)
    $('#main').html(view.render().el)
```

routerからviewに複数のHogeを渡しています。
@collection.fetch(reset: true) が肝です。後で解説します。

3つ目に、collectionを処理するviewの定義。

```coffeescript:assets/javascript/views/hoges/hoges_index.js.coffee
class RailsProject.Views.HogesIndex extends Backbone.View

  template: JST['hoges/index']

  initialize: ->
    @collection.on('reset', @render, this)

  render: ->
    $(@el).html(@template())
    @collection.each(@appendHoges)
    this

  appendHoges: (hoge) ->
    view = new RailsProject.Views.Hoge(model: hoge)
    $('#hoges').append(view.render().el)
```

routerのところで言っていた、肝の部分ですが、initializeで@collectionのリセットが走ったらrenderメソッドを実行するように書いています。これは、Ajaxで@collectionを取ってくる前に、一度renderメソッドが実行されるため、@collectionが取得できたら再度renderを実行させるためです。
routerの@collection.fetchメソッドで、reset: trueに設定していたのは、フェッチし終わったらリセットしたと認識させるためです。私が参考にしていたサンプルだとこの部分が省略されていたので、すごくハマりました。バージョンが変わってfetchしたときのデフォルトの動作が変わったのかもしれません。

appendHogesメソッドで、4つ目で定義するクラスをインスタンス化して、ul#hogesに詰め込んでいます。そのul#hogeは、template: JST['hoges/index']で読み込んでいます。

では、templateを定義します。

```erb:assets/templates/hoges/index.jst.eco
<ul id="hoges"></ul>
```

このul#hogesの中にリストを表示するのがゴールとします。

4つ目に、個々のmodelを処理するviewを書きます。

```coffeescript:assets/javascript/views/hoge.js.coffee
class RailsProject.Views.Hoge extends Backbone.View
  template: JST['hoges/age']
  tagName: 'li'

  render: ->
    $(@el).html(@template(hoge: @model))
    this
```

このviewで読み込むtemplateを定義します。

```erb:assets/templates/hoges/hoge.jst.eco
<%= @hoge.get('name') %>
```

backbone.jsで、モデルの要素を取りいく場合は、model.get('attribute_name')になります。model.attribute_nameというふうにはいかないので、注意が必要です。

5つ目に、このルーターを起動させます。

```coffeescript:assets/javascript/hoges.js.coffee
$ ->
  # hogesコントローラーのときだけ実行するように書いたとして…
  if $('#hoges_controller').is('*')
    window.router = new RailsProject.Routers.Hoges()
    Backbone.history.start()
```

これで、railsプロジェクトのhogesコントローラーにアクセスするとリストが表示される…はず…。