Mithril.jsをRailsで始める

[Mithril](https://lhorie.github.io/mithril/) は名前を聞いたことがある程度で、特に触ってみる予定はなかったのですが、[Shibu's Diary: 世界最速でMithril本をリリースした話](http://blog.shibu.jp/article/160479412.html) を見て俄然興味が湧いたので、早速、[黒ムツ本](http://www.oreilly.co.jp/books/9784873117447/) を購入して読みました。
コンパクトで大変読みやすかったです。オススメ。[コードもMITライセンスで公開](https://github.com/oreilly-japan/mithril-book-sample) されていて嬉しい。

# あえてRailsで

本を読んでなんとなく理解できたところで、次は実際に触ってみるフェーズ。
今後いろいろといじっていくにあたり、サーバサイドは使い慣れているRailsでやりたい。
ということで、まずはTodoのサンプルをRails化してみました。

全コードはこちらにあります。
https://github.com/tnantoka/my-mithril

# やったこと

## Mithrilを入れる

[mrsweaters/mithril-rails](https://github.com/mrsweaters/mithril-rails) は古かっ**た**[^1]し、今回は[MSX](https://github.com/insin/msx) を使う予定もなかったので、rails-assets で入れました。

```rb:Gemfile
source 'https://rails-assets.org' do
  gem 'rails-assets-mithril'
end
```

```
$ bundle
```

```ja:app/assets/javascripts/application.js
//= require mithril
```

## コンポーネントを書く

`app/assets/javascripts/components/todo.coffee`に置きました。

### モデル

```coffeescript
class Todo
  constructor: (data = {}) ->
    @description = m.prop(data.description || '')
    @done = m.prop(false)

  @list: ->
    m.request(method: 'GET', url: '/todos', type: Todo, initialValue: [])
  @save: (list) -> 
    data =
      todos: list.filter (t) -> !t.done()
    MyMithril.request(method: 'POST', url: '/todos', data: data)
```
`MyMithril.request`は`csrf-token`を追加して`m.request`を呼ぶ自作関数です。https://github.com/tnantoka/my-mithril/blob/master/app/assets/javascripts/my-mithril.coffee

mithril-rails使えば勝手にやってくれる？（未確認）

### ビューモデル

Enterキーで保存できるようにしています。

```coffeescript
vm =
  init: ->
    vm.list = Todo.list()
    vm.description = m.prop('')
    vm.entered = m.prop(false)
  add: ->
    if vm.description().length
      todo = new Todo(description: vm.description())
      vm.list().push(todo)
      vm.description('')
      Todo.save(vm.list())
  toggle: (value) ->
    @done(value)
    Todo.save(vm.list())
  onkeyup: (value) ->
    vm.description(value) unless vm.entered()
    vm.entered(false)
  onkeypress: (e) ->
    if e.keyCode == 13
      vm.entered(true)
      vm.add()
    else
      m.redraw.strategy('none')
```

### コントローラー

```coffeescript
controller = ->
  vm.init()
```

### ビュー

onchangeはfocusがある間は発生しない（忘れててちょっとハマった）ので、onkeyupで入力中のキーワードをvm.descriptionに設定しています。

```coffeescript
view = ->
  m 'div', [
    m '.row', [
      m '.col-sm-4', [
        m '.input-group', [
          m 'input.form-control', {
            value: vm.description()
            onkeyup: m.withAttr('value', vm.onkeyup)
            onkeypress: vm.onkeypress
          }
          m 'span.input-group-btn', m 'button.btn.btn-primary', onclick: vm.add, 'Add'
        ]
      ]
    ]
    m 'div', vm.list().map (t) ->
      textDecoraton = if t.done() then 'line-through' else 'none'
      m '.checkbox', [
        m 'label', [  
          m 'input[type=checkbox]', onclick: m.withAttr('checked', vm.toggle.bind(t)), value: t.done()
          m 'span', { style: { textDecoration: textDecoraton } }, t.description()
        ]
      ]
  ]
```

## コンポーネントを呼び出す

手抜きでViewから呼んでます。

```js:app/views/welcome/todo.html.slim
javascript:
  m.mount($('#app').get(0), components.todo.component);
```

## サーバーサイドを実装する

createでは受け取ったデータをsessionに突っ込んで、indexではそれをそのまま返しています。

```rb:app/controllers/todos_controller.rb
class TodosController < ApplicationController
  def index
    render json: session[:todos].to_a
  end

  def create
    session[:todos] = params[:todos]
    render nothing: true
  end
end
```

## テストを書く

それなりに長いので割愛。
https://github.com/tnantoka/my-mithril/blob/master/spec/javascripts/todo_spec.coffee にあります。

実行には、[Teaspoon](https://github.com/modeset/teaspoon) を使いました。README通りやればCoverageもちゃんと取れてよかったです。
Sinon.JSの`server.respondImmediately = true`がなぜか効かなかった（？）り、npm module使えるの？とかまだわかってないところだらけですが。


## 動いた！

http://my-mithril.bornneet.com/ で動いています。

tweet-boxの方は[React.js Introduction For People Who Know Just Enough jQuery To Get By · React for Designers](http://reactfordesigners.com/labs/reactjs-introduction-for-people-who-know-just-enough-jquery-to-get-by/) の劣化コピーです。（Rails関係ない）

# 感想

まだ触り始めたばかりですが、[vue.js](http://vuejs.org/) ぐらい手軽に利用できて、しかもrouter[^2] も付いてきて、設計指針も示してくれる（これは本が親切なのもありますが）ので、片手間フロントエンダーには大変ありがたいと思いました。

だいぶ慣れてきたので、次は何か大きめのものを作ってみる予定。

[^1]: 僕がMithrilの勉強を始めた先週末時点では数ヶ月放置されてた。今はちゃんと最新版にアップデートされてるみたい。
[^2]: ラウターと読む癖を付けよう！