読者です 読者をやめる 読者になる 読者になる

is Neet

ネトゲしながら暮したい

SICP読むぞ!

ぼくにとってプログラミングという存在は, どうしても達成したい事があってしょうがなく25~6歳くらいで始めたので全く基礎教養が足りていない状況にあるし, なにより僕は素で知性が低い.
周りの高意識体は大体Lispがどうのとか, 深夜アニメがどうとか言っているので取り敢えず形から入ろうと思って, ぼくも深夜アニメは観るよう心がけているがどうも知性の高まりを感じないし, モモキュンソードとか観てると寧ろ知的生命体としての尊厳を脅かされてヤバミを感じ始めたので, Lispとかいう真理に触れてみようと思い立ちSICPを読んでみることにした.
読みながらメモとるといいよみたいな事オススメされたので, Githubリポジトリ作って章事にファイル作ってメモと写経取りながら理解を進めていこうと思う.
取り敢えず最初の方だけ読んだ感想としては, なんかすごそうだったし面白かった(低知性)

soplana/sicp · GitHub

いつまで続くかなー...

承認欲求満たすのを自動化した

昨日の夜, 承認欲求をアウトソーシングして外部の人間に任せているのは不安要素が多いのでBOTがやるべき仕事だと思って, 久しぶりに夜更かしして承認くんを作った.

https://twitter.com/ShouninJp

フォローしておくとたまに承認してくれる.

承認くんはAPIも用意されていて, 承認単語の登録・削除・一覧の機能を備えている.

# 登録
$ curl http://www.shounin.jp/vocabularies -X POST -d 'text=これが才能か...'

# 削除
$ curl http://www.shounin.jp/vocabularies/1 -X DELETE

# 一覧
$ curl http://www.shounin.jp/vocabularies

誰でも自由に承認くんに単語を覚えさせたり出来るのでよろしくお願いします.

友人が作ったベーコンを頂いた

f:id:soplana:20140804084234j:plain

割りと長い付き合いになるネトゲ友達がいて, 彼が自宅の庭に燻製用のナニカを作ったらしい.
ベーコンやらウィンナーやらを沢山作っているっぽくて, おすそ分けしてもらった.

彼は福岡にいるんだけど, ベーコンと一緒に明太子とかポテチの九州しょうゆ味とか送ってくれて福岡に親戚ができたようでテンションあがって, こっちからも何か送るかと考えたけど, 東京ってこういう時困るよなと思った.
なに送ろう?

f:id:soplana:20140804084220j:plain

ベーコン, 1kgも送ってくれて消費しきれないかもと不安になっていたけど案外コンスタントに食べてたら一週間ちょっともあれば消費できそうな量で安心した.
単純に何も調味料つけずに焼いて食べてもしっかり味がついてて歯ごたえもあってめちゃくちゃ美味しいし, 最近は毎朝目玉焼きと一緒に食べてて最高の人生っぽくなってる.

そういや一緒にやってたネトゲでも彼は生産職をやっていて, 彼は当時僕よりずっと高レベルだったので初期に必要な回復アイテムなどを大量に作って頂いたりしていたし, ネトゲもリアルもやっぱり人間性出ていいなと思った.
僕は当時ゲーム内や2chの某ネトゲスレで迷惑行為をひたすら繰り返してたんだけど今は随分落ち着いた.
あの頃もニートだったなー.

QOLアイテムまとめ(前編)

二ヶ月くらい前に自分の中で不意に何かが弾けて部屋のモノを片っ端から全部すてた.
ゴミ袋20袋分くらい捨てたし不用品回収業者に来てもらって大きい物も処分した.
もともとプログラミングとネトゲ特化型の部屋だったのにも嫌気がさして, もっと人間らしい部屋にしようと決めて行動を開始した.
SIerを辞めた瞬間にスーツを捨てる心境に多分近くて, ちょっと人生色々あったので決別の意味を込めて色々捨てて新しいアタシに生まれ変わろうと思った.

それで, 今回は部屋を大改修していく中で印象的だったQOLアイテムを列挙していこうと思う.
ここ二ヶ月くらい貯金の減りがマッハだけど気にしない事にした.
今後もアップデートしていきたい.

BDレコーダー / BDZ-ET2100

http://www.amazon.co.jp/dp/B00G91GFYO

深夜アニメを録画したかった.
おまかせ・まる録機能を使うと「深夜アニメ」などといったキーワードで, 片っ端から全部録画しておいてくれる.
この機能の恩恵を受けるためには2TBくらいの容量と2-3チャンネルの同時録画機能が必須だと思った. それらを満たすのがこの機種だったので買った.
用事があったりして観れないアニメがあった時でもストレスを感じなくなったし, だいぶ満足している.

Apple TV

http://www.amazon.co.jp/dp/B004BR2CL8

今更だけど買った.
iTunesの曲を手軽に再生できたり, Apple製品のミラーリングが簡単なので自宅でハッカソンを開催する時などにポテンシャルを発揮する.
huluでひたすらPlanet Earth(だっけ?)とかそういう番組を流し続けるのも良いと思う.
ハリウッド版のリングとか観たりした.
結局AppleTVを買ったことで, もともとあったオーディオを捨てされたのでスッキリした.

スピーカー / HT-XT1

http://www.amazon.co.jp/dp/B00JOVCTKK

アニメを録画出来る環境とAppleTVで音楽や映画を再生出来る環境を整えたら買わずにはいられなくなるのがTVに繋げるスピーカーだった.
当たり前だけどTVのスピーカーよりは断然音がクリアで, アニメ・音楽・映画ライフは最高のものに近づいた.
値段と音質のコスパについて, そこまで詳しい訳でもないが十分満足できた.

本棚

http://www.nitori-net.jp/shop/goods/goods.aspx?goods=6020132

以前使っていた本棚がそのロールを果たさなくなってきてたので, 代わりにそれなりに大きい本棚を買った.
漫画や技術書も全部入ったし満足しているけど, ココ最近本は全部Kindle or PDFで買うからなーとも思う.
あるいは本買ったとしても自炊してdropboxだしなー.

windows 8.1

http://www.amazon.co.jp/dp/B00FKRJ946/

SSD / インテル SSD 730 Series 240GB

http://www.amazon.co.jp/dp/B00IF4NGEU/

2年くらいマトモに使ってなかったデスクトップのwindows7をアップデートしようと思った.
どうせやるならクリーンインストールでいいやと思い, HDDをSSDに交換した. HDDとか昭和かよ.
起動とかめっちゃ早くなってテンション上がったけどwindowsでやること一個もなくて結局limechat入れてircやるくらいしか使ってない.
windows store周り, デスクトップで使ってる上では害悪でしかないと思った.
なんかネトゲやろっかなー.

iTunes Match

https://www.apple.com/jp/itunes/itunes-match/

MacBookも二台あったしiPhonewindowsでも音楽を同期したかった.
年間4,000円くらいだし別にいいかと思って登録したら, 全てのデバイスでクラウド上の音楽データにアクセス出来る生活は, 今までの生活が何かの悪い冗談だったんじゃないかと思う程に人間らしい生活が訪れた.

人をダメにするソファー

http://www.amazon.co.jp/dp/B0083F91XQ

無印に買いに行ったら品切れで入荷待ち状態と言われた.
結構昔からあるアイテムなのに何故と思ったけど, ココ最近インターネットで流行ったからだろうな.
しょうが無いからamazonでニセモノっぽいものを買った.
想像よりQOLの上がり具合は高く無かった.
一ヶ月くらい使うとビーズから張りが無くなってきて, 一年後とか考えるとちょっと萎える感じもした.
一万円くらいだしまぁそんなもんかと言い聞かせながら使っている.
腰への負担も良くないらしいし, 普通のソファー買ったほうがいいと思う.

hue

http://www.amazon.co.jp/dp/B00BB946H2

スマホから操作出来る電球だ.
蛍光灯の紐ひっぱったり, 壁についてる電気のスイッチぽちぽちしたりするのも全然未来感なくてクソダサイと思ってたので丁度良かった.
初期セットは25,000円と少々高めだけど原始的な生活からの脱却と考えるとそれなりに納得できたので買った.
もともと間接照明で生活していたのもあって満足度が非常に高い.
電球の色を自在に変化させられるのは思ったよりずっと素敵で, 自分が落ち着く色合いや明るさの度合いを各電球それぞれに設定出来るので部屋のイメージがグッと良くなってモテそうな気がした.
IFTTTと連携することで, 例えばTwitterで自分にリプライが来た時に色を変化させるなどといったことも出来そうだ.

IRKit

http://www.amazon.co.jp/dp/B00H91KK26

家にある各リモコンの信号を記憶して, エアコンやTVなどをスマホで操作出来るようにするガジェット.
エアコン, TV, BDレコーダー, Apple TV, スピーカーなどリモコン周りのエントロピーが非常に高まっていて困っていたので買った.
基本的にかなり人間らしい生活に近づいたと思うので満足しているのだけれど, いくつか不満点もあった.
まずよく使うボタン全部を登録するのが面倒で, 頑張って記憶させたとしても別デバイスからリモコンを操作しようと思った時にまた同じように登録しなおす必要がある. 上手いこと同期してくれたらいいなと思った.
あと微妙に繊細な動きが不得意なのか, BDレコーダーのメニューを操作していると張り切り過ぎの新人みたいな空回りをしたりする.

オーディオインターフェイス / MR816CSX

http://www.amazon.co.jp/dp/B001F92CTM

物心ついた時から音楽はずっとやっていた気がするのだけど, 最近はプログラミングばかりになっていて音楽趣味の方に全く時間が取れてないなかった.
不意に, MaxMSPなどをやり始めてしまいDTM熱が盛り上がってきたので買った.
しかしそれなりに良いスピーカーやDAWも再度揃え直さないといけないので金のかかる趣味だなと思う.
以前はcubaseを使っていたけど, Logicに乗り換えてみようと思う.

番外編

  • テレビ台やラグなども多趣味仕様に切り替えた. 大変満足している.
  • 2mくらいのL字のソファーを処分したら2万円かかった. ボラれた気がする.
  • 今までデスクと椅子が無かった(ソファーに座る事しか想定してなかった)ので買った.
  • 椅子に関しては年収に比例していいものにしていきたい. 年収をあげていきたい.

今後ほしいもの

  • 良い扇風機
  • 賢い体重計
  • 宅配ボックス
  • 除湿機, 加湿器
  • 責務を果たすオーブンレンジ
  • やる気のある掃除機
  • ちゃんと乾く乾燥機つきの洗濯機

↑この辺も揃ったらまた後編書きたい

言語化できる人が頭の良い人だと思う

日々色々な媒体で文章に触れていて思う事がある.
当たり前の話なのだけれど, ブログ, 記事, 小説, エッセイ, 2ch, Twitter, Tumblrなどで素敵な文章を読む度に,「なるほど. 言い得て妙だ」と思う.


大体, 人はぼんやりと自身を取り巻く事象に関して何かしらの感想を持って生きている(と思いたい).

Twitterはなんか好き」
facebookはなんか嫌い」
「猥褻物の3Dデータを配布するのは別にいいんじゃない?」
「いや良くない」

などなど.


そして, 「なるほど」と思う文章というのは「なんか良い」「なんか悪い」という小並感漂う抽象度の高い感想を, より具体的に言語化して説明できている場合なんじゃないかと思う.


自分の周りの人でも話していて「なるほど」と思う瞬間が多い人というのは, 例え話が上手だったり, 普段自分がモヤモヤしてる「なんとなくそう思ってる」部分に対して気持ちいいほどしっくりくる言語化をしてくれる人だったりする.


子育ての苦労話を延々とされても, 子育て経験は愚かマトモな交際経験すらない僕には何も響かない.
とはいえ子育てが大変そうなのは何となくの印象として持っている.
そのぼんやりした印象をハイコンテクストな共感を期待して説明するのではなく, 「理解」に落とし所を設定し, そして理解を最大まで共感に近づける事が出来る人もいるだろう.
それが出来るのが頭の良い人なのだろうなと思う.



これを少し敷衍して考えるとある危険性に気づく.
だいたいそうやって頭の良い人に言語化してもらったあと「ホンマそれ」と思ったり, あるいは有識者が書いた記事やブログ何かを読んで「それな」と思ったりする.
面白いツイートをRTした後に「ホンマこれ」ってツイートするアレだ.

冒頭でも書いたように, 誰しも自身を取り巻く事象に関して何かしらの感想は大体持っているので, たまに目にする頭のいい人が書いてる文章読んで「俺もそう思うわ」って言って頭の良い人と同じステージで物事捉えられているつもりになるのって危ないと思う.

ここで僕が表現したい事も, 微妙にニュアンスを伝えきれてないような気もするし, 頭の良い人に書きなおして欲しい.

angularjsのunitテストの書き方【$resource編】

さて前回の記事、「Rails4.0でangularjsを使ってRESTfulなajaxを実装する」に引き継ぎ今回はangularjsのテストについて書こうと思う。
題材には引き続き前回の内容を使うので、そちらを読んでからのほうが読みやすいかと。
ちなみにangularjsにしてもテストにしても自分自身かなり手探りで進めているため、不自然な点や改善点があればどんどん伺いたい。

実行環境

angularjsはtestableだと謳われている割にビルドインされたテスト実行環境がないので自力で整える必要がある。(しょうが無い気もする) 今回はテスト実行環境にkarma、テストフレームワークはjasmineを使用する。
前提としてnodeはインストールされているものとする。

$ npm install -g karma
$ npm install -g karma-ng-scenario

以上でkarmaがインストールされた。
次に設定ファイルだ。

まずはspec/以下にjavascriptsというディレクトリを作ろう。

$ mkdir spec/javascripts

次に設定ファイルの編集だ。

$ vim spec/javascripts/karma.conf.js
module.exports = function(config) {
  config.set({
    basePath: '../..',

    frameworks: ['jasmine'],

    autoWatch: true,

    preprocessors: {
      '**/*.coffee': 'coffee'
    },

    files: [
      'app/assets/javascripts/jquery.js',
      'app/assets/javascripts/angular/angular.js',
      'app/assets/javascripts/angular/angular-mocks.js',
      'app/assets/javascripts/angular/*',
      'app/assets/javascripts/admin/widgets.js'
      'spec/javascripts/**/*_spec.js.coffee'
    ],
    exclude: [
      'app/assets/javascripts/angular/angular-scenario.js'
    ]
  });
};

さていくつか設定ファイルの段階で注意しておかないといけないことがある。
まず、filesというoptionに渡すjsファイルの置き場所についてだ。
railsサーバがローカルで走っている状態であれば

files: [
  ...
   'http://localhost:3000/assets/angular/angular.js'
  ...
]

上記のような指定でも構わない。
ただ、テストを実行させる上で毎回必要になるmoduleが増える度に設定ファイルにincludeするのが面倒だし、テストの実行環境がいつでも3000番portだとは限らない。
それでもangularjsやjQueryだけの問題であればgoogle CDNなどを指定してもいいのかもしれないけれど、CDNで管理されていないpluginなどもincludeする必要が出てきた時にそれらだけfile path指定になるのも違和感があるので、面倒だけどassets以下で管理することにして話を進める。
取り敢えずテストを実行したいのだ。こまけぇこたぁいいんだよ!
なので面倒ではあるが、assets以下のそれっぽいディレクトリにangularjsや必要になるプラギンが纏まってる前提で話を進める。

さてその上で次に問題になるのが、'app/assets/javascripts/angular/*',でangularjs関連の全てのファイルをincludeしてしまうとファイルの読み込み順と、テストに不必要なファイルまで読み込んでしまうことだ。
前者にはangular.jsangular-mocks.jsを先に指定して読み込むことで対応し、後者にはexclude optionにangular-scenario.jsを指定することで解決する。

あとテストはどうしてもその構造上ネストが深くなり可読性が下がる傾向にあるので、怠惰を極めたSyntaxのcoffeeの方が相性が良いと思うのでcoffeeで進める。
長々と話したが実行環境を整えるSTEPはそんなに大変ではない。

実行してみる

一度前回のエントリーで紹介したサンプルスクリプトの内容を見てみよう。

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

// csrf tokenの設定
sampleApp.config(function($httpProvider){
    $httpProvider.defaults.headers.common['X-CSRF-Token'] =
        $('meta[name=csrf-token]').attr('content');
});

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();
})

ではまず、最初に全ての基盤となるsampleAppモジュールが存在することを確認するテストを書いておこう。
ディレクトリ名やファイル名は適当でいい。

# spec/javascripts/module/sample_app_spec.js.coffee

describe "sampleApp module", ->
  sampleApp = null

  beforeEach ->
    sampleApp = angular.module('sampleApp')

  it '存在すること', ->
    expect(sampleApp).not.toBeNull()

実行してみよう。

$ karma start spec/javascripts/karma.conf.js
INFO [karma]: Karma v0.10.9 server started at http://localhost:9876/
INFO [Chrome 33.0.1750 (Mac OS X 10.8.5)]: Connected on socket v38eh7FqGBdYR2QSEY0x

karmaが起動するとhttp://localhost:9876/にアクセス出来るはずなので、一度ブラウザでこのURLを開いておくとそれ以降はファイルの変更を自動検知してテストが走ってくれる。

Chrome 33.0.1750 (Mac OS X 10.8.5): Executed 1 of 1 SUCCESS (0.374 secs / 0.078 secs)

上記のようなメッセージが出れば成功だ。
次に、sampleApp moduleは、ngResourceに依存することを宣言時に指定しているのでコレもテストしておこう。

# spec/javascripts/module/sample_app_spec.js.coffee

it '依存moduleの確認', ->
  expect(sampleApp.requires).toContain('ngResource')

ngResource以外にも今後増えていくならこのit句に、expect(sampleApp.requires).toContain('ngSanitize')のような形で増やしていけばいい。

angularjsのunitテストは何をテストするモノなのか

angularjsでajaxのテストを書く場合のポイントをまずは知っておこう。

  • 実際のリクエストはmock化する。
  • viewの見た目の変化に関してはdirective(と$scope)の仕事としてロジックと完全に切り離すことで、testableになっている。

上記を踏まえた上で今回テストしたいことを考えてみる。

f:id:soplana:20140301230108p:plain

今回のサンプルアプリケーションは、「機能を使う」「機能を削除する」のボタンが押される度にサーバにリクエストが飛び、データを更新してボタンがtoggleするというシンプルなものだ。
ではそのボタンのtoggleはどのように実装されていたか。

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

viewにかかれてある'ng-hide'=>'widget.active''ng-show'=>'widget.active'によってtoggleは実装されている。
widgetが使われていない時、widget.activeはfalseとなり、widgetが使われている時に、widget.activeがtrueになる。
これを利用してボタンのshowとhideを制御することで実現されている。

この場合、ボタンの表示非表示を制御しているのはng-hideng-showのdirectiveだ。
つまり事実これらの要素の表示非表示切り替えがうまくいくかどうかに関しては我々がテストで担保する所ではない。
angularjsを信じるしかないのだ。大丈夫、信じよう。

こうしてdirective(と$scope)とロジックを切り離して処理が記述出来る事により、我々がテストで担保しないといけない部分は単純に「リクエストの結果、widget.activeのtrue/falseが切り替わるかどうか」に集中すればよい。
素晴らしい。
仮にココに「通信中はボタンがdisabledになること」を証明したいケースが追加されたとしても、ng-disabledというdirectiveが 存在するので考え方としては同じでいい。

テストを書いてみよう!

それでは実際にテストの内容をみてみよう。

# spec/javascripts/resource/widget_spec.js.coffee

describe "widget", ->
  Widget = null

  beforeEach module('sampleApp')
  beforeEach inject ($injector)->
    Widget = $injector.get('Widget')

  it 'widgetモデルが存在する', ->
    expect(Widget).not.toBeNull()

まずはWidgetが存在することを確認。unitテストではinjectを使って、依存性の注入を行う(多分)。
次にいよいよモックを使ったテストを追記していく。

# spec/javascripts/resource/widget_spec.js.coffee

  describe 'widgetのon/off', ->
    url = '/admin/widgets/blog.json'
    blog = null

    describe '機能を有効にする', ->
      response = {id: 1, _type: 'blog', active: true}

      beforeEach inject (_$httpBackend_)->
        _$httpBackend_.expectPOST(url).respond(response)
        
        blog = new Widget(_type: 'blog', active: false)
        blog.$save()
        _$httpBackend_.flush()

      it 'activeになること', ->
        expect(blog.active).toBeTruthy()

基本的にテストを書く上ではbeforeには「振る舞い」を、itには「テスト内容”のみ”」を記述するべきだと考えているので極力その理念にしたがって書いていく。
この例ではitでblogと名づけたwidgetのactiveプロパティの変化をテストしている。まぁそこは読めばわかると思う。
ポイントとなるのはbeforeで行っているモックの宣言周りだと思う。

では_$httpBackend_とは何なのか。
これはhttp requestをモック化してくれたり、それに対応するresponseを設定しておくことが出来るmoduleだ。
_$httpBackend_を使うときに大まかな流れは、

  • _$httpBackend_にリクエスト先とレスポンスを設定する
  • リクエストが実際に飛ぶアクションを実行する(この時点ではリクエストは発生しない)
  • _$httpBackend_.flush()を呼びだし、リクエストを実行する

になる。
なのでまずはexpectPOSTexpectGETの引数にURLを指定し、.respondにresponseデータを設定しておく。
次にblog = new Widget(_type: 'blog', active: false)widgetを、active = falseの状態で生成しておく。
次にblog.$save()で、本来なであればリクエストが実際に飛ぶ処理を呼び出す。
そして最後に_$httpBackend_.flush()を呼び出すことによって、リクエストが発行されresponseが返る。

これらのbeforeが処理された後、itの中でactiveプロパティがtrueに変更されていることをテストする。

$removeで発行されるDELETEの処理も同様に書ける。

DELETEのテストも追加してみよう

    describe '機能を無効にする', ->
      response = {id: 1, _type: 'blog', active: false}

      beforeEach inject (_$httpBackend_)->
        _$httpBackend_.expectDELETE(url).respond(response)
        
        blog = new Widget(_type: 'blog', active: true)
        blog.$remove()
        _$httpBackend_.flush()

      it 'non activeになること', ->
        expect(blog.active).toBeFalsy()

はい。大差ないですね。

次回は

今回は$resourceのテストの書き方を紹介した。
controllerのテストはまた少し違ったポイントがあるのでまた次回紹介したい。
あとはdirectiveとかserviceとかに関してもまだあんまり触れられてないのでその辺も書いていきたい。

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

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

画面には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-showng-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-showng-hidewidgetが持っている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()みたいに)

一度話はそれるがRailsWidgetモデルを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についてもう少し話したい。

追記

railsアプリケーションでPOST, PUT, DELETEを行う場合CSRF対策でcsrf-tokenが必要になる。
そこでangularjsからRequestを送る場合、毎回パラメータにcsrf-tokenを含めるようにしなければならない。
当然毎回DOMから取得してRequestを送信してもいいのだけれど、面倒なのでangularjsから送るRequestにはHeaderにtokenを必ず埋め込むように設定してあげると楽だ。

// csrf tokenの設定
sampleApp.config(function($httpProvider){
    $httpProvider.defaults.headers.common['X-CSRF-Token'] =
        $('meta[name=csrf-token]').attr('content');
});