LoginSignup
22

More than 5 years have passed since last update.

ember-railsを試してみる。

Last updated at Posted at 2013-07-09

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

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

参考にしたURLはこちら。
Rails + Ember.js

Railsプロジェクト作成

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

rails new EmberRails --database=sqlite3 -T

Gemfileに以下を追加。

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

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

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

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

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

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

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も自動で作られる。

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を編集して、ルートを教える。

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も別ファイルに定義する。

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

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

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

事前データ登録

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

rails c

適当に作ろう。

Todo.create(content: 'テスト1')
Todo.create(content: 'テスト2')
Todo.create(content: 'テスト3')
Todo.create(content: 'テスト4')
Todo.create(content: 'テスト5')

サーバ起動

Webrickを起動する。

rails s

テンプレート作成

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

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

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

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を定義する。
(ルーティングとかはまだだけど先に作る。)

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

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

スクリーンショット 2013-07-09 15.02.08.png

データの登録

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

View側

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

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から値を取得する。
何もデータがなければ登録しないようにしてある。

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

ポチッとな。

スクリーンショット 2013-07-09 16.37.40.png

追加された。

データの削除

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

Controller側

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

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で指定するみたいである。
というわけで、以下のように削除ボタンを追加した。

<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

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

スクリーンショット 2013-07-09 17.01.14.png

はい、消えました。

データの更新

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

View側

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

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が呼ばれると、編集中に変わるようにする。

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

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

再びView側

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

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メソッドを呼び出す。

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メソッドで更新処理がサーバに送られる。

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

ダブルクリックすると、

スクリーンショット 2013-07-09 17.53.59.png

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

スクリーンショット 2013-07-09 17.53.26.png

編集完了。

やってみて

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22