ember-railsを試してみる。

岡山Ruby会議の懇親会で、Ember.jsがいいよ。これからbackbone.jsをやるくらいだったら、Ember.jsのほうをオススメするよと言われていたので、Ember.jsを試してみようと思ってやってみた（みている）。

ちなみに私はEmber.jsの事前知識はほとんどないので注意。

参考にしたURLはこちら。
[Rails + Ember.js](http://www.devmynd.com/blog/2013-3-rails-ember-js)

## Railsプロジェクト作成

まずは、railsプロジェクトを作る。

```bash:
rails new EmberRails --database=sqlite3 -T
```

Gemfileに以下を追加。

```rb:Gemfile
gem 'ember-rails'
gem 'ember-source', '1.0.0.rc6.2'
```

bundle install したあと、ember用のテンプレートを作る。

```bash:
bundle install
rails g ember:bootstrap -g --javascript-engine coffee
```

config/environments/development.rbに以下を追加する。

```rb:config/environments/development.rb
config.ember.variant = :development
config.handlebars.precompile = false
```

とりあえず、Todoでscaffoldしてみようと思う。
また、/用のコントローラーも作る。

```bash:
rails g scaffold todo content:string closed:boolean
rails g controller home index
bundle exec rake db:migrate
```

scaffoldすると、Ember.js用のファイルも生成される。

Ember.jsで使われるtodoのmodelも自動で作られる。

```coffeescript:app/assets/javascripts/models/todo.js.coffee
# for more details see: http://emberjs.com/guides/models/defining-models/

EmberRails.Todo = DS.Model.extend
  content: DS.attr 'string'
  closed: DS.attr 'boolean'
```

ember-railsならば、最初からRestでアクセスするようになっているので、
store.js.coffeeは編集せず。

## Ember.js用ファイルを編集

### ルーティング

router.js.coffeeを編集して、ルートを教える。

```coffeescript:app/assets/javascripts/router.js.coffee
# For more information see: http://emberjs.com/guides/routing/

EmberRails.Router.map ->
  @resource('todos', ->
    @resource('todo', {path: ':todo_id'})
  )
```

Todosのrouteも別ファイルに定義する。

```coffeescript:app/assets/javascripts/routes/todos_route.js.coffee
EmberRails.TodosRoute = Ember.Route.extend({
  model: ->
    EmberRails.Todo.find()
})
```

ここで、Home#indexがrootになるように、ルーティングを修正する。

```rb:config/routes.rb
EmberRails::Application.routes.draw do
  root to: "home#index"
  resources :todos
end
```

### 事前データ登録

表示するデータが欲しいので事前に登録しておく。

```bash:
rails c
```

適当に作ろう。

```rb:
Todo.create(content: 'テスト１')
Todo.create(content: 'テスト２')
Todo.create(content: 'テスト３')
Todo.create(content: 'テスト４')
Todo.create(content: 'テスト５')
```

### サーバ起動

Webrickを起動する。

```bash:
rails s
```

### テンプレート作成

さて、Ember.jsでのテンプレートエンジンは、handlebars.jsである。
このテンプレートを全部読み込むようにするために、templatesの下に、all.jsを作る。

```js:app/assets/javascripts/tempaltes/all.js
//= require_tree .
```

Ember.jsのテンプレートを使う場合、デフォルトでapplication.hbsがlayoutファイルになるようである。
先にこれを作っておく。

```html:app/assets/javascripts/templates/application.hbs
<header id="header">
    <h2>{{#linkTo "index"}}HOME{{/linkTo}}</h2>
    <nav>
        <ul>
            <li>{{#linkTo "todos"}}Todos{{/linkTo}}</li>
        </ul>
    </nav>
</header>
<div id="content">
{{outlet}}
</div>
```

{{outlet}}に、サブのViewが表示される模様。

リンクのTodosをクリックしたときに表示されるViewを定義する。
（ルーティングとかはまだだけど先に作る。）

```html:app/assets/javascripts/templates/todos.hbs
<h1>Todos</h1>
<ul>
{{#each todo in controller}}
    <li>{{todo.content}}</li>
{{else}}
    <li>まだありません。</li>
{{/each}}
</ul>

{{outlet}}
```

### 表示してみる

http://localhost:3000 にアクセスしてみる。

![スクリーンショット 2013-07-09 15.00.43.png](https://qiita-image-store.s3.amazonaws.com/0/10078/e011f075-a075-2e8a-f091-d5cd6fdd0d5b.png)

Todosのリンクを押してみる。

![スクリーンショット 2013-07-09 15.02.08.png](https://qiita-image-store.s3.amazonaws.com/0/10078/b2542c3d-d979-148c-e58b-557b96799dc3.png)

## データの登録

データの登録は、Ember.jsのコントローラー経由で行える。

### View側

まずはViewを修正する。
Ember.TextFieldなどを使うとよい。
登録ボタンについては、Ember.Buttonがあったので使ってみたところ、非推奨になっていた。
{{action "hoge"}}を使ってねということらしい。これをbuttonと組み合わせる。

```html:app/assets/javascripts/templates/todos.hbs
<h1>Todos</h1>
{{view Ember.TextField id="new-todo" placeholder="何をする？" valueBinding="newContent"}}
<button {{action 'createTodo'}}>登録する</button>
<ul>
{{#each todo in controller}}
    <li>{{todo.content}}</li>
{{else}}
    <li>まだありません。</li>
{{/each}}
</ul>

{{outlet}}
```

### Controller側

createTodoアクションを、コントローラーに作成する。コントローラーはTodosController。
View側で設定したvalueBindingから値を取得する。
何もデータがなければ登録しないようにしてある。

```coffeescript:app/assets/javascripts/controllers/todos_controller.js.coffee
EmberRails.TodosController = Ember.ArrayController.extend
  createTodo: ->
    content = this.get('newContent')
    return unless (content.trim())
    todo = EmberRails.Todo.createRecord({
      content: content
    })
    this.set('newContent', '')
    todo.save()
```

これで、登録するボタンを押したら登録される。

![スクリーンショット 2013-07-09 16.37.30.png](https://qiita-image-store.s3.amazonaws.com/0/10078/f9a74a02-628e-a061-4dae-a6a318c26d0c.png)

ポチッとな。

![スクリーンショット 2013-07-09 16.37.40.png](https://qiita-image-store.s3.amazonaws.com/0/10078/2fa596a5-c723-a9cf-f948-d077e2a806a9.png)

追加された。

## データの削除

データの削除も登録と同じように、コントローラー経由で行う。
ただし、新規登録のときとは違い、ArrayControllerを継承したコントローラーではなく、ObjectControllerを継承したコントローラーに依頼する必要がある。

### Controller側

というわけでTodoControllerを作成する。
メソッドにremoveTodoを定義。

```coffeescript:app/assets/javascripts/controllers/todo_controller.js.coffee
EmberRails.TodoController = Ember.ObjectController.extend
  removeTodo: ->
    todo = this.get('model')
    todo.deleteRecord()
    todo.save()
```

### View側

次にView側。eachで todo in controller とやっていたのだが、これだとうまくいかなくなった。
each controller だと、モデルオブジェクトが省略されて、いきなりメンバ変数にアクセスできるっぽいのだが、メンバ変数名をcontentにしていたらダメだった。content.contentにしないと表示されない（これは俺がTodoモデルを作ったときのカラム名の失敗か…）。
省略されたmodelに紐づくコントローラーをitemControllerで指定するみたいである。
というわけで、以下のように削除ボタンを追加した。

```html:
<h1>Todos</h1>
{{view Ember.TextField id="new-todo" placeholder="何をする？" valueBinding="newContent"}}
<button {{action 'createTodo'}}>登録する</button>
<ul>
{{#each controller itemController="todo"}}
    <li>
        {{content.content}}
        <button {{action "removeTodo"}} class="destroy">削除</button>
    </li>
{{else}}
    <li>まだありません。</li>
{{/each}}
</ul>

{{outlet}}
```

これで、いけるはず。

![スクリーンショット 2013-07-09 17.01.01.png](https://qiita-image-store.s3.amazonaws.com/0/10078/d1164a58-2d07-4aee-1c7d-689133a9ad12.png)

新しいタスクの削除ボタンをポチッとな。

![スクリーンショット 2013-07-09 17.01.14.png](https://qiita-image-store.s3.amazonaws.com/0/10078/8e451d07-ab74-de5e-c31e-4fe7be33cb05.png)

はい、消えました。

## データの更新

Todoをダブルクリックしたらテキストエリアにするようにしてみます。

### View側

Todoの状態が変わったら表示を変える設定を、Viewに設定します。
bindAttrで、Todoの状態が変わったら（編集中になったら）liのclassが変わるようにする。
また、isEditingの状態が変わったら表示するViewを変えるようにする。
Todoの内容をダブルクリックしたら、TodoController#editTodoを呼び出すようにする。

```html:app/assets/javascripts/templates/todos.hbs
<h1>Todos</h1>
{{view Ember.TextField id="new-todo" placeholder="何をする？" valueBinding="newContent"}}
<button {{action 'createTodo'}}>登録する</button>
<ul>
{{#each controller itemController="todo"}}
    <li {{bindAttr class="isCompleted:complated isEditing:editing"}}>
        {{#if isEditing}}
            <input type="text" class="editing">
        {{else}}
            <label {{action "editTodo" on="doubleClick"}}>{{content.content}}</label>
            <button {{action "removeTodo"}} class="destroy">削除</button>
        {{/if}}
    </li>
{{else}}
    <li>まだありません。</li>
{{/each}}
</ul>

{{outlet}}
```

### Controller側

コントローラーにTodoの編集中という状態を持たせる。
ダブルクリックでeditTodoが呼ばれると、編集中に変わるようにする。

```coffeescript:app/assets/javascripts/controllers/todo_controller.js.coffee
EmberRails.TodoController = Ember.ObjectController.extend
  isEditing: false
  removeTodo: ->
    todo = this.get('model')
    todo.deleteRecord()
    todo.save()

  editTodo: ->
    this.set('isEditing', true)
```

この状態で、ダブルクリックしてみると…。
![スクリーンショット 2013-07-09 17.41.04.png](https://qiita-image-store.s3.amazonaws.com/0/10078/4de32168-da8d-3400-957a-33a975870979.png)

テキストエリアになった。オブジェクトの状態を見張る処理がテンプレート内に定義されている。

### 再びView側

このままだと更新できないので、Viewを変更する。
テンプレートからViewを呼び出す。Viewの定義はviews以下にすることができる。
valueの値を既存のデータをbindしてあるので編集するときに反映される。

```html:app/assets/javascripts/templates/todos.hbs
<h1>Todos</h1>
{{view Ember.TextField id="new-todo" placeholder="何をする？" valueBinding="newContent"}}
<button {{action 'createTodo'}}>登録する</button>
<ul>
{{#each controller itemController="todo"}}
    <li {{bindAttr class="isCompleted:complated isEditing:editing"}}>
        {{#if isEditing}}
            {{view EmberRails.EditTodoView valueBinding="content.content"}}
        {{else}}
            <label {{action "editTodo" on="doubleClick"}}>{{content.content}}</label>
            <button {{action "removeTodo"}} class="destroy">削除</button>
        {{/if}}
    </li>
{{else}}
    <li>まだありません。</li>
{{/each}}
</ul>

{{outlet}}
```

呼び出されるViewの内容はこれ。Ember.TextFieldを継承してあり、処理が終わるとTodoControllerのacceptChengesメソッドを呼び出す。

```coffeescript:app/assets/javascripts/views/edittodo_view.js.coffee
EmberRails.EditTodoView = Ember.TextField.extend
  classNames: ['edit']
  insertNewLine: ->
    this.get('controller').acceptChanges()

  focusOut: ->
    this.get('controller').acceptChanges()

  didInsertElement: ->
    this.$().focus()
```

### 再びController側

コントローラーでは、acceptChangesが呼ばれたときの処理を実装するだけ。
ここで更新処理を実装する。
編集が終わったのでisEditingをfalseに戻し、modelを保存している。
modelが保存されたら自動的にputメソッドで更新処理がサーバに送られる。

```coffeescript:app/assets/javascripts/controllers/todo_controller.js.coffee
EmberRails.TodoController = Ember.ObjectController.extend
  isEditing: false
  removeTodo: ->
    todo = this.get('model')
    todo.deleteRecord()
    todo.save()

  editTodo: ->
    this.set('isEditing', true)

  acceptChanges: ->
    this.set('isEditing', false)
    this.get('model').save()
```
![スクリーンショット 2013-07-09 17.51.57.png](https://qiita-image-store.s3.amazonaws.com/0/10078/6f19849b-f900-ce6a-54b2-91a83a8aec95.png)

ダブルクリックすると、

![スクリーンショット 2013-07-09 17.53.59.png](https://qiita-image-store.s3.amazonaws.com/0/10078/b287f471-a025-d009-d5cc-68f7aedbe6c0.png)

元のデータを反映しつつ、

![スクリーンショット 2013-07-09 17.53.26.png](https://qiita-image-store.s3.amazonaws.com/0/10078/aa9b1d6f-b3d8-0d11-c18f-863262807f85.png)

編集完了。

# やってみて

なんとなく、本当になんとなくだが、backbone-railsよりはember-railsのほうがわかりやすい気がする。
画面側はEmber.jsでcoffee書きながらバリバリやっていって、
最初はRestAdapterではなく、FixtureAdapterでモックを作るようにして、
サーバ側が実装されたらRestAdapterに置き換える、というやり方で分業ができるんかなーと
思った。まぁFixtureAdapter試してないんだが・・・。