Rails4.0でangularjsを使ってRESTfulなajaxを実装する


長文注意。
angularjsについて今更ながらに触り始めて色々と感動したので纏めておく。
angularjsがどういったフレームワークかは公式のチュートリアルを眺めてたらぼんやりと把握できると思うので今回その辺の話はあまり触れない。
http://angularjs.org/

  
angularjsのAPIについては公式のドキュメント含めて様々なメディアやブログに取り上げられているが、導入から体系的に語られてるものはあまり無い印象だったので、僕のブログでは導入から具体的な目的に沿った実装方法を紹介していこうと思う。  
ちなみに自分のangularjsへの理解も触り始めて一週間程度なのでだいぶ甘い。  

## angularjsを一週間やってみた感想
最初の2日くらいがだいぶつらい。
飲み込みが早い人ならすぐに使いこなすのかもしれないが、angularjsはdirective, controller, filter, resource, serviceなどのキーワードが沢山出てきて、そして本当にどれも重要だったりする。
とにかく、公式のドキュメントを読みまくって手を動かすしか無い。
あとこの辺とか読んだ。英語よくわからないけどまぁなんとなく読んだ。
[AngularJS in 60 Minutes](http://fastandfluid.com/publicdownloads/AngularJSIn60MinutesIsh_DanWahlin_May2013.pdf)
  
3日目くらいから感動の連続だった。
ほんとこれすごいとおもった（小並感）

## Rails4にangularjsを導入する

sampleのrailsアプリの`Rails.root/app`以下はこんな感じだと想定する。

```
.
├── assets
│   ├── javascripts
│   │   ├── application.js
│   │   ├── widgets.js
│   └── stylesheets
│       ├── application.css
├── controllers
│   ├── application_controller.rb
│   ├── widgets_controller.rb
├── models
│   ├── widget.rb
└── views
    ├── layouts
    │   └── application.html.haml
    └── widgets
        └── index.html.haml
```


まずはGemfileに

```
gem 'angular-rails-engine'
```
を追記して、`bundle install`をして、application.jsに

```
//= require angular/angular
//= require angular/angular-resource
```
を追記する。
あと僕の環境ではangularjsをproduction環境で動かす際に`config/environments/production.rb`に

```
config.assets.js_compressor = :uglifier
```

を設定しないとエラーがでて動かなかった。


## 取り敢えずHello World
### angularjsのスコープを設定する

angularjsは自身がviewのどの部分を管理するかを知る必要がる。
逆に言うと、サイトの大部分はjQueryやbackboneなどで構築してるけど部分的にangularjsを導入してみたい、などの要件にもスコープの設定ができるがゆえに簡単に対応することが出来きる。
まずは`app/views/layouts/application.html.haml`に、

```
!!!
%html{'ng-app' => 'sampleApp'}
```

`sampleApp`というネームスペースをhtmlタグに宣言する。
次に`app/views/widgets/index.html.haml`に以下の内容を記述する。

```
%div{"ng-controller" => "WidgetsController"}
  {{ hello }}
```

divに`"ng-controller"`という属性でネームスペースを宣言することで、このdiv以下の要素はangularjsの管理下にある事をangularjs自身は知ることが出来る。
`"ng-controller"`以下のviewではjavascript expressionが自在に呼び出せるようになる。
上記のviewでは`{{ … }}`で囲まれた部分がexpressionとして評価されhelloという変数の内容をhtml上に表示することが出来る。

### viewで宣言したスコープとjsを紐付ける

`"ng-controller"`以下の`hello`に値を結びつける処理は以下になる。

```
//./app/assets/javascripts/widgets.js
var sampleApp = angular.module('sampleApp', []);

sampleApp.controller('WidgetsController', function($scope){
    $scope.hello = 'Hello World';
})

```

まず一行目の

```
var sampleApp = angular.module('sampleApp', []);
```
によって`%html{'ng-app' => 'sampleApp'}`で宣言したモジュールを作成する。

3行目以下の処理によりviewで宣言したhelloに値を代入する。

```
sampleApp.controller('WidgetsController', function($scope){
    $scope.hello = 'Hello World';
})
```

次に`sampleApp.controller`でviewで宣言したcontrollerである`'WidgetsController'`を生成し、第二引数のfunctionにわたってくる`$scope`という変数を経由してview（html）側のscopeにアクセスでき、`$scope`を経由して変更された値は「リアルタイム」でviewに反映される。
$scopeで管理できるものは変数だけでなくfunctionでもいけるので例えば、

```
// view
%div{"ng-controller" => "WidgetsController"}
  {{ hello('soplana') }}
```

```
//./app/assets/javascripts/widgets.js
sampleApp.controller('WidgetsController', function($scope){
    $scope.hello = function(name){
        'Hello '+name+'!!';
    }
})
```

のような事も可能だ。

## directiveを使ってみる

まだ僕自身directiveって何だよ感があるんだけど、現段階の理解としては「DOMに対する操作、あるいはテンプレートそのもの」を指すと考えている。
directiveは「DOMに対する操作、あるいはテンプレートそのもの」をDOM属性や要素名によって表現する。
いくつか例をあげてみる。
#### ng-clickを使う

`$('#hoge').on('click', function(){})`みたいな事がしたい場合

```
// view
%div{'ng-click' => 'click()'}
  hoge

//./app/assets/javascripts/widgets.js
sampleApp.controller('WidgetsController', function($scope){
    $scope.click = function(){}
})
```

#### ng-hideを使う

`$('#hoge').hide()`みたいな事がしたい場合

```
// view
%div{'ng-hide' => 'true'}
  hoge
```

#### ng-repeatを使う

要素を繰り返し出力したい

```
// view
%ul
  %li{'ng-repeat'=>'widget in widgets'}
    {{ widget.name }}

//./app/assets/javascripts/widgets.js
sampleApp.controller('WidgetsController', function($scope){
    $scope.widgets = [
        {name: 'widget1'},
        {name: 'widget2'},
        {name: 'widget3'}
    ]
})

// 出力結果
<ul>
  <li>widget1</li>
  <li>widget2</li>
  <li>widget3</li>
</ul>
```

この他にも非常に強力で便利なdirectiveが沢山容易されている。
また、自分でcustom directiveを作成して使うことも可能だ。

## ここまでで一旦まとめ

基本的にはdirectiveを使って、controllerに定義したclickイベントを呼び出したり、DOMを操作したりする使い方が多そうだ。

そうなるとcontrollerがだんだんfatになっていく問題が浮上するが、それについてはいくつか対処法があり

- そもそも細かい単位でcontrollerを作っていく
- DIを利用する
- custom directiveを使う

などが考えられる（他にもあったら or 違うだろ！みたいなのがあったら教えてください）。
DIの話も面白いので次回まとめます。

## angularjsでajax

やっとか…疲れた…。

### railsのrouting

以下のような何の変哲もないroutingを想定する。

```
GET    /widgets(.:format)     widgets#index
POST   /widgets/:id(.:format) widgets#update
DELETE /widgets/:id(.:format) widgets#destroy
```

※本来はPUTでupdateされるべきだが、PUTが出てくると内容が濃くなりすぎるのでPOSTに変更させてもらう

### 処理フロー

まず画面はこんな感じだ

![sampleApp](http://cdn-ak.f.st-hatena.com/images/fotolife/s/soplana/20140301/20140301230108.png "sampleApp")

画面にはwidgetが7つ並んでおり、それぞれに「この機能を使う」「この機能を削除する」というアクションボタンが用意されている。
この画面でやりたいことは以下になる。

- htmlのロードが完了したら、ajaxによりwidget一覧を取得する
- widget一覧を画面に出力する
- 「この機能を使う」をユーザがクリックした場合
  - ajaxでPOSTリクエストを出しwidgetモデルを使用可能状態にupdateする
  - ボタンを「この機能を削除する」に変更する

さて、これをjQueryで実装することを想像すると簡単な処理とは言えど、ロジックとDOM操作が入り組んだ保守性の悪いコードが容易に想像できる。（綺麗に書ける人も当然いるだろうけども）

### view書いちゃう

まずはview

```
%div{"ng-controller" => "WidgetsController"}

  .row
    .col-lg-12
      %h4.page-header
        %i.fa.fa-gavel.fa-fw
        機能の追加・削除

  .row
    .col-lg-4{'ng-repeat'=>"widget in widgets"}
      .panel.panel-default.widget
        .panel-heading
          %span.glyphicon.glyphicon-user
            {{ widget.label }}
          %button.btn.btn-info
            この機能を使う
          %button.btn.btn-warning
            この機能を削除する
        .panel-body
          {{ widget.description }}
```

viewはdirectiveの章で扱った`ng-repeat`の例とさほど変わらない。
すこしhamlの要素が増えただけだ。
これによりcontrollerで`widgets`という変数に値を入れれば、繰り返し処理が行われhtmlに出力してくれることになる。

次にボタンをクリックした時の挙動のことを想像してみよう。
一連のフローで必要になるdirectiveは、

- ajaxリクエストを出すイベント発火用の`ng-click`
- showとhideをtoggleさせる為の`ng-show`と`ng-hide`

が必要になるっぽいので追加しちゃう。

```
%div{"ng-controller" => "WidgetsController"}

  .row
    .col-lg-12
      %h4.page-header
        %i.fa.fa-gavel.fa-fw>
        機能の追加・削除

  .row
    .col-lg-4{'ng-repeat'=>"widget in widgets"}
      .panel.panel-default.widget
        .panel-heading
          %span.glyphicon.glyphicon-user>
            {{ widget.label }}
          %button.btn.btn-info{'ng-click'=>'widget.save()', 'ng-hide'=>'widget.active'}
            この機能を使う
          %button.btn.btn-warning{'ng-click'=>'widget.remove()', 'ng-show'=>'widget.active'}
            この機能を削除する
        .panel-body
          {{ widget.description }}
```

`ng-show`と`ng-hide`はwidgetが持っている`active`というbool値を渡す事でtoggleを実現できそうだ。

### js書いちゃう

```
// ./app/assets/javascripts/widgets.js
var sampleApp = angular.module('sampleApp', ['ngResource']);

sampleApp.factory('Widget', function($resource){
    var Widget = $resource('/widgets/:id.json', {id: '@id'});
    return Widget;
});

sampleApp.controller('WidgetsController', function($scope, Widget){
    $scope.widgets = Widget.query();
})
```

はい、三行目あたりから`factory`やら`$resource`やら良くわからないのが出てきましたね。
この辺がcontrollerをfatにしない為の仕組みの一つであるDIなんだけど、今回はそこは掘り下げて話さない。
次回頑張る。

まず`var Widget = $resource('/widgets/:id.json', {id: '@id'});`この行によってWidgetクラスが生成される。
誤解を恐れずにいうなら、angularjsにおけるajax通信部分はrailsで考えるとmodelに相当するものだと思った。
`$resource`を使って生成されたオブジェクトには以下のメソッドが追加される。

```
Widget.get();    // 特定のデータを取得する   : GET
Widget.save();   // 特定のデータを更新する   : POST
Widget.query();  // 複数件のデータを取得する : GET
Widget.remove(); // 特定のデータを削除する   : DELETE
Widget.delete(); // 特定のデータを削除する   : DELETE
```

これらは`$resource`の第一引数に渡したURLにRESTfulにアクセスできるメソッドだ。
また上記のメソッドは、Widgetクラスのインスタンスにもコピーされる。
インスタンスからこれらのメソッドにアクセスする場合は、prefixとして$がつく。（`widget.$save()`, `widget.$remove()`みたいに）

一度話はそれるがRailsでWidgetモデルをupdate and deleteすることを考えてみよう。
以下のようになるはずだ。

```
widget = Widget.find(2)
widget.active = true
widget.save   #=> updateされる

widget = Widget.find(2)
widget.delete #=> deleteされる
```

考えてみれば`find`によってMysqlなりmongoなりにコネクションを貼りデータを取得してオブジェクトを操作して結果をまたDBに通知している。
angularjsの`$resource`が行うこともこれと何ら変わりない。
ただ問い合わせる先がDBでなくWebアプリケーションであるだけだ。

### $resourceについて考えてみる

```
sampleApp.factory('Widget', function($resource){
    var Widget = $resource('/widgets/:id.json', {id: '@id'});
    return Widget;
});
```

ここはWidgetクラスの宣言に過ぎない。
`$resource`の第二引数に渡す`{id: '@id'}`は第一引数の`:id`に対応しており、`@`をつけて宣言することでWidgetクラスのインスタンス.idから取得せよ、という宣言になる。
`id`を渡さない場合は`:id`部分が無視されて、`'/widgets.json'`
に対するアクセスになってくれる。
インスタンスが`widget.id`のように値を持っているpropertyを持つ場合は`/widgets/5.json`のようなURLを自動生成してくれる。

```
sampleApp.controller('WidgetsController', function($scope, Widget){
    $scope.widgets = Widget.query();
})
```

ではこれはどうか。
上記で話した通り`Widget.query()`は`$resource`が付与するメソッドであり、複数件のデータを取得する場合に用いられる。
この例だと、二行目は`/widgets.json`にGETでリクエストを発行するので対応するRailsアクションで

```
 render json: @widgets
```

など複数件のwidgetをrenderするようにしておく。

### callbackは書かない

```
$scope.widgets = Widget.query();
```

まぁ書いてもいいんだけど、上記のように書いてもqueryメソッドが裏側でajax通信を非同期で始めても完了したかどうかはプログラマは知らなくても良い。
この時点でまず空の参照をquery()が返し、`$scope.widgets`に入れる。
後のことはcontrollerの仕事ではなく`$scope`に処理を委譲するのだ。
無事レスポンスが返ってくる事で`$scope`がdirectiveに通知しテンプレートに反映する。

### 改めて処理フローを考える

さて、ここまでの事を踏まえて改めてviewのコードをみると、なんとなく分かるはずだ。

```
    .col-lg-4{'ng-repeat'=>"widget in widgets"}
      .panel.panel-default.widget
        .panel-heading
          %span.glyphicon.glyphicon-user>
            {{ widget.label }}
          %button.btn.btn-info{'ng-click'=>'widget.$save()', 'ng-hide'=>'widget.active'}
            この機能を使う
          %button.btn.btn-warning{'ng-click'=>'widget.$remove()', 'ng-show'=>'widget.active'}
            この機能を削除する
        .panel-body
          {{ widget.description }}
```

注目すべき点は以下の二行になる。

```
%button.btn.btn-info{'ng-click'=>'widget.$save()', 'ng-hide'=>'widget.active'}
  この機能を使う
%button.btn.btn-warning{'ng-click'=>'widget.$remove()', 'ng-show'=>'widget.active'}
  この機能を削除する
```

`ng-click`により呼び出される`widget.$save()`は、`ng-repeat`によりeachやfor文のように繰り返されている、Widgetクラスのインスタンスから呼び出しているメソッドだ。
もっと具体的にいうとRailsでrenderしたjsonデータの一件一件がココに入ってくる。
つまりこれらのインスタンスは`widget.id`というプロパティを持つことが保証されているので、

```
var Widget = $resource('/widgets/:id.json', {id: '@id'});
```

この宣言により`'/widget/5.json'`というURLが、インスタンスが持つpropertyから組み立てられPOSTリクエストが発生することになる。
さらにレスポンスが返ってくるとこのインスタンスは自動でアップデートされる。
さらにさらに$scopeがそのアップデートを検知して、`ng-show`および`ng-hide`のbool値を更新し、要素を隠したり出したりしてくれる。

ちなみにRailsでは、対応するアクションでそれぞれ

```
 render json: @widget
```

と更新されたwidgetオブジェクトのjsonを返すようにするのを忘れてはならない。

## とにかく処理の記述量が少なく済む

これだけの内容ならほんの数行のjavascriptを書くだけで実装できてしまった。
DIなどの仕組みによりコードの再利用性も高く、可読性も高い。
ちょっと思いの外長文になりすぎて疲れたのでこの辺で今回は勘弁しといてやるか…。


最後に、railsのprecompileによりjsがMinifyされるとangluarjsがうまく動作しない場合があるので、その対策としてcontrollerの宣言には以下の様なシンタックスも容易されている。
本番運用を考えて最初からこっちで書いておくといいかもしれない。

```
sampleApp.controller('WidgetsController',  ['$scope','Widget', function($scope, Widget){
    $scope.widgets = Widget.query();
}])
```

次回はDIについてもう少し話したい。