taiyoh's memorandum

@ttaiyoh が、技術ネタで気づいたことを書き溜めておきます。

Ukigumo + hubot-ircでビルド結果を通知

 hubotをIRC通知するやり方はあちこちで書かれてるが、僕としてはhubot-irc-runnableを使った方がカジュアルなのでオススメ。
 →jgable/hubot-irc-runnable · GitHub
 その上で、scripts/httpd.coffeeに以下のコードを追加する

  #http://chobie.hatenablog.com/entry/2012/02/26/125532
  robot.router.post "/hubot/say", (req, res) ->
    room = req.body.channel
    message = req.body.message
    #user = robot.userForId room # これだと動かない
    user = robot.adapter.userForId room
    user.room = room
    user.type = 'groupchat'
    robot.send user, "#{message}"
    res.writeHead 200, {'Content-Type': 'text/plain'}
    res.end 'OK'

こうすると、POST /hubot/sayにリクエストを送ればmessageパラメータの内容をchannelパラメータで指定したチャンネルに対して投稿してくれる。これでhubotにもikachanの機能が追加される。
なので、

$app->push_notifier(
    Ukigumo::Client::Notify::Ikachan->new(
        url     => 'http://example.com/hubot',
        channel => '#your-channel',
        method  => 'say'
    )
);

 Ukigumo::Clientのpush_notifierでNotify::Ikachanがそのまま使えるようになる。methodの指定が必要だから、同梱されてるukigumo-client.plとは別でスクリプトを用意しないといけないけど、/hubot/noticeのパスで受け取れるようにしておけばそのまま使える。

socket.ioで下層パスで通信する

 メモ書き。例えば/path/toなんてパスの下でコンテンツの表示もsocket.ioの通信もしたい時。

// in node
var app_path = "/path/to/app";
io.set('resource', app_path);
io.of(app_path).on('connect', function() {
  // do something
});

 とやって、表示するHTMLでsocket.io.jsを取得する際に

<script src="/path/to/app/socket.io/socket.io.js"></script>
<script type="javascript">
var socket = io.connect('http://' + location.hostname, {
  'resource': 'path/to/app'
});
</script>

 という感じで指定する。
 キモはnode側でresourceとofメソッドの引数に同じものを入れることと、クライアント側でio.connectのオプションにnode側と同じ(でも頭の/はない)resourceを入れておくことか。
 別のパスで別のsocket.ioのアプリを立てたい時なんかに使える。

Perlでのfactory_girlもどきのもの

 ちょっと作ってみた。
 → taiyoh/p5-Test-Factory · GitHub
 PerlだとO/R Mapperが乱立しまくってて何かに依存したモジュールにするのは汎用性が低すぎてないわー、って感じだったので、DBISQL::MakerでDB毎の処理は抽象化させて、SQL的にはinsertだけに絞って機能を提供するようにした。その点ではRubyActiveRecordの影響力が絶大で楽。
 factory_girlほど高機能じゃないけど、作ったパターンをベースに統一されたインターフェイスからデータオブジェクトを生成できる(&DBにそのデータを突っ込む)、ってことについてはとりあえずできる。

package Foo;

use Test::Factory;

my $foo_counter = 0;

factory foo => {
    col1 => 'hoge',
    col2 => 'fuga',
    col3 => 'piyo',
    col4 => sub { ++$foo_counter }
};

package main;

# O/R Mapperではないので、内部でDBI使って自前でinsertする
Foo->dsn("dbi:mysql:dbname;host=localhost", 'db username', 'db password', {});

my $f1 = Foo->build('foo');
print $f1->created; # => 0 (false)
print $f1->col4; # => 1


my $f2 = Foo->build('foo');
print $f2->col4; # => 2

$f1->save; # insert into foo (col1, col2, col3, col4) values (?, ?, ?, ?) /* hoge, fuga, piyo, 1 */

my $f3 = Foo->create('foo');
print $f3->created; # => 1 (true)
print $f3->col4; # => 3

Amon2のコントローラをRailsっぽくしてみる

 ただしルーティングはDispatcher::RouterSimple限定だけど。

これをコントローラクラスでuseしておくと、before_filterとafter_filterが使えるようになる。(ちゃんとテスト書いてないので思いつきレベル)
ただ、Module::Functions::get_public_functionsを使ってる都合で、フィルタとして使う関数はprivate methodとしてprefixに「_」を付けておかないと、public methodと見なされて登録されてしまうのが注意点。

自作のオートマトンのモジュールをCPANに上げた

 承前→ 有限オートマトンの習作として - taiyoh's memorandum
 半年くらい前にこんなエントリ書いていたんですが、気づいたら2月中旬に知らない人がFSM::Simpleって名前でCPANに上げてて、しかも僕の実装より全然シンプルじゃなくて腹が立ったので、"FSM::Tiny"ってモジュール名に変更してアップしました。
 → http://search.cpan.org/~taiyoh/FSM-Tiny/
 ホントはこんなyak shavingじゃなくて、FSM::Tinyを使ったネタがあるんだけど、ブログに書くにはちょっと長いし説明するのがメンドイ。

node-fiberでライフチェンジングとか煽ったことを若干後悔してる

 承前→ node.jsのvmとsynchronizeはライフチェンジング - taiyoh's memorandum
 @hokaccha先生の記事読んで、やっぱインフルエンサーが書くと内容の充実度も反応も全然違うなー、とか思ってたのですが、特にあの記事をぶくましてるけどfiber使ったことない人は、使いどころをちゃんと見極めた方がいい、という注意喚起。もしくは過去の自分の記事に対する懺悔。
 実は以前qiitaにこっそり投稿してたんだけど、以下の処理の時にエラーが発生する。

// sync_test.js
var sync = require('synchronize');

sync.fiber(function() {
  try {
    sync.await((function(defer) {
      console.log("await 1 start");
      setTimeout(function() {
        console.log("await 1 end");
        try {
          sync.await((function(defer2) {
            console.log("await 2 start");
            setTimeout(function() {
              console.log("await 2 end");
              defer2(null, 'ok');
            }, 1000);
          })(sync.defer()));
          console.log("await 2 over");
          defer(null, "ok");
        }
        catch(e) {
          console.log("await 2 error", e);
          defer(e, null);
        }
      }, 1000);
    })(sync.defer()));
  } catch(e) {
    console.log("await 1 error", e);
  }
  console.log("await 1 over");
});

 ずらずら長いが、端的に言えば、sync.await中にもう一回sync.awaitしたらどうなるか、ということを検証したもの。結果は以下のとおりになる。

% node sync_test.js
await 1 start
await 1 end
await 2 error [Error: no current Fiber, defer can'b be used without Fiber!]
await 1 error [Error: no current Fiber, defer can'b be used without Fiber!]
await 1 over

 これだけだとピンとこないかもしれない。じゃあ以下のパターンがあった時は?

  • ちょっと大きめのDBのselect文をsync.awaitで発行してる最中に、別のリクエストでsync.awaitを使ってファイルへの書き込みをする
  • OAuth認証の処理をsync.awaitで発行してる最中に別のリクエストでsync.awaitを使ったselect文を発行する

 他にも色々あると思うけど、ふつーにこういうウェブアプリケーション書いてしまうと、確実にエラーが発生し、「テストでは通ってるけど、本番に上げたらなんかちょくちょくエラーが起きる」という事態が発生する。シングルスレッドでこなすnodeならではの現象だと思う。PerlとかRubyみたいにpreforkしてリクエストを処理したらどうなるかはわからない(そもそもできるのか?このへんは自分はよく知らない)。でも、そういうアプリケーションを書く人は、よっぽどの変態か「自分はJS以外の言語を知らないから」という消極的な理由でアプリケーション実装を選択してるか以外にないだろうと思う。
 要は、プロセス中のシーケンスが単一であればいい。なので、バッチスクリプトを書くときなんかは、すごく効果的なんじゃないかと思う。バッチ実装で非同期とか考えないでいい筈なのにコールバック地獄とか、ただの面倒事でしかないので。

ただのメモ書き、またはArk::ModelsライクなものをAmon2でも用意する

 調子に乗ってAmon2::Modelsとかあるといいかも、って思ってたけど、

package MyApp::Models;

use strict;
use warnings;

use Object::Container '-base';

sub import {
  $_[1] = 'model'; # or something
  goto \&Object::Container::import;
}

register config => sub {
  shift->ensure_class_loaded('MyApp::Config');
  MyApp::Config->current;
};


package MyApp::Config;

use Config::ENV 'PLACK_ENV';

1;

 これで大体やりたいことはできる感じがしてきた。