rrails v1.0.0 released.

以前公開した、rails/rakeコマンドを超早くするrrailsですが、
http://d.hatena.ne.jp/walf443/20120427/1335482420

あれから、色々な方に使っていただいているようで、パッチをいくつかもらったりしてupdateしたりしています。

最近quark-zjuさんという方からたくさんpatchをいただいて、今までできなかったことができるようになった & 今までと非互換なところがでてきた、ということで紹介します。

rails console/serverができるように

今まで子プロセスへPTYの引きつぎをしていなかったため、rails console/serverなどが実行しても、こちらの入力をrrailsへ教えることができなかったのですが、できるようになりました。

個人的にはrails consoleあんまり使わないので対応しなくてもいいかなーと思って放置していたところですが、使えるとまぁ便利かなとは思います。

rrails-serverコマンドは非推奨になり、rrails startで起動するように

guard-rrailsつかっているとあまり関係ないですが、rrails-serverコマンドはそのうちなくなると思われるので、rrails startを使うようにしてください。

TCPSocketではなく、UNIXDomainSocketをデフォルトで使うように

rrailsとの通信は今までTCPでの通信でしたが、デフォルトではUnixDomainSocketを使うようになりました。

これにより、複数のプロジェクトでのrrailsの立ちあげが楽になると思われます。
一方で、rrailsを実行する際に、対象のプロジェクトのディレクトリへcdしないと適切に実行できなくなっています。

まとめ

使い方が色々かわっている(なるべくは互換性残すようにはがんばったつもりだけど...)ので、今まで使っていた方はあらためてREADMEを読みなおすことを推奨します。
http://walf443.github.com/rrails/

https://github.com/walf443/rrails/blob/master/Changes.md

DCIについて思うこと

最近DCI(Data Context Interaction)アーキテクチャというのを良く耳にするので、ちょっと調べたり他の人のコード例などをもとにちょろっと書いてみたりしたところ、やっぱりよくわからないなーと思いつつ、つまりはこういうこと?というのをまとめてみる。

たぶん色々と大分間違っているんだろうなぁと思いつつ、間違いを指摘してもらえるとありがたいです。

  • DCIは、MVCを置きかえるものではない。MVCのModelの部分が肥大化し、管理が難しくなる傾向にある(Skinny Controller, Fat Model)ので、そこをうまく扱うにはこう考えて分割すればよろしい、という考え方。つまり、M(D-C-I) - Controller - Viewみたいに書くことになる。
  • 同じモデルでも、ユーザーの役割によって、振舞いが異なることがある
    • 例えば、管理者(Administrator)には見えてよいが、閲覧者(Viewer)には見えてはいけないコンテンツ(Content)
  • 振舞いが異なるものを1つのモデルにして管理しようとすると、混乱が生じる
    • 例えば、Contentに、AdministratorおよびViewerのものをごっちゃに記述すると、Administrator権限のものをViewerで呼んでしまうようなミスが発生しやすくなる
  • そこで、コントローラーからはそのコンテキストに応じたモデルを呼びだすようにしよう
    • 例えば、ViewingContentクラスへViewerがContentを扱う際の情報を記述し、ControllerからはViewerの文脈であれば、ViewingContentを呼びだすようにする。

DCIで検索してみて出てくるコードを参考に書いたりしつつ、疑問に思ったことなど。

  • DCIという名前は個人的にはわかりづらくてMVCほど直感的には感じない気がする
    • コード例をみてみるに、ContextはUseCase /InteractionはRoleと記述されていることが多くて、私もUseCase/Roleの方がわかりやすいなと思う。ただし、Roleのクラス名をうまく考え出すことが難しくてユーザーに関連するようなケース以外だとあまりロールなしでUseCaseだけでよいのではないかと感じた。
  • コード例をみると、UseCaseはModelとRoleを紐づけることだけに専念するように書かれているが、サンプルが簡単な例だからか無駄にレイヤーが増えているだけで個人的にはあまりわかりやすくなったようには感じない。
  • コンテキストというのはユーザーの役割に応じて発生するものと思われるので、誰に対しても同じような振舞いをするものはあえてコンテキストを書く必要はない気もする
    • 実際はそういうものが後々複雑になったりしがちなのがではあるので、一枚噛ませておくのはいいと思う。
  • こういうときに個人的にはDCIで書くのがよさそうだなーという例
    • Userモデル (同じユーザーでも文脈に応じて違うクラスとして振る舞うように書くと便利そう)
    • 公開/非公開などのビジネスロジックを持つモデル
      • 非公開のものを誤って公開することがないように、コンテキストをコードへうまく表現できるとミスが減る

とはいえ、もっとコード書いてみないと、なんともいえないなーという感じではあります。
DCIでバリバリ書かれている本格的なアプリケーションで良い例とかあったりするのかな...

参考: http://d.hatena.ne.jp/digitalsoul/20100131/1264925022

gitのtagを元にChangeLogっぽいものを出力する

ChangeLogはリリースのたびに見直して手で丁寧に書いておく方が利用者にとってはうれしいことが多いですが、一方でまとめなおし作業はまぁまぁめんどいです。

そこで、手直しするにしてもそれまでの情報をコミットログから雛形を自動生成してやれないかなーとおもってつくってみました。

https://github.com/walf443/dotfiles/blob/master/bin/git-changelog

基本的には標準出力に出すので、自動生成して、git add -pしてひとまず欲しいところだけ取りこんで、記述を微調整すればよいのかなー、という感じです。

生成例はこんな感じ

https://github.com/walf443/seqdiag.js/blob/master/Changes.md

ChangeLogのないライブラリの更新がどんな感じかなーと眺めたりするのにもまずまず便利です。(ただしcloneしてくる必要がありますが)

git log --pretty=formatだけで頑張れないかなと思いつつ、プログラム書いた方が早そうだった。(眺めるだけならそれで十分かも

適度に実行時間を抑制するrate_throttle

cronなどで統計処理のSQLをたくさん投げたりするときに、mysqlにあまり負荷をかけたくないので、適度にsleepを挟みながら実行させる、という処理を書くときに、perlだと、Sub::Throttleというid:kazuhookuさんのライブラリを使っていたのですが、rubyで同様のライブラリを探すとなかったので、rubyrate_throttleという名前で移植しました。(本当はProc::Throttleにするつもりだったのですが、既にあったので....)

require 'rate_throttle'

1000.times do
    # limit block to 10% workload.
    RateThrottle.throttle(0.1) do
        # do something.
    end
end

こんな感じで書くと、ブロックを実行した後にその実行時間が10%になるようにsleepを入れてくれます。ブロック内の実行時間が1秒であれば、sleepは9秒で、合計10秒になる、という塩梅です。

crawlerとかを書いているときに適度にsleepを挟むといったときなどにも使えるのではないでしょうか。

参考: http://labs.cybozu.co.jp/blog/kazuho/archives/2008/08/sub-throttle.php

text型のカラムを明示的にINSETRTしなかったときのNULL値の挙動

後輩に訊かれてどうだったっけ?と思って調べたので、まとめておく。

まとめ

  • text型でINSERT時に明示的に情報を入れない場合、カラムがNOT NULLであったときは、""が入る。NOT NULLでない場合はNULLが入る。

作業ログ

# not null制約をつけている場合
mysql> create table fuga ( id int unsigned not null AUTO_INCREMENT primary key, text text not null ) Engine=InnoDB;
Query OK, 0 rows affected (0.13 sec)

mysql> insert into fuga () values ();
Query OK, 1 row affected, 1 warning (0.06 sec)

mysql> select * from fuga;
+----+------+
| id | text |
+----+------+
|  1 |      |
+----+------+
1 row in set (0.00 sec)

mysql> select @@version;
+-----------+
| @@version |
+-----------+
| 5.1.58    |
+-----------+
1 row in set (0.02 sec)

# not null制約をつけなかった場合
mysql> create table foo ( id int unsigned not null AUTO_INCREMENT primary key, text text );                                                                                                               

Query OK, 0 rows affected (1.28 sec)

mysql> insert into foo () values ();                                                                                                                                                                      
Query OK, 1 row affected (0.02 sec)

mysql> select * from foo;
+----+------+
| id | text |
+----+------+
|  1 | NULL |
+----+------+
1 row in set (0.00 sec)

mysql> 
mysql> select * from foo;
+----+------+
| id | text |
+----+------+
|  1 | NULL |
+----+------+
1 row in set (0.00 sec)

fork時のrand()の挙動

perlでforkしたときに、srand呼ばないと親プロセスとseedの値が同じになってしまうので、子プロセスの間でrand値が変わらなくてハマる、という経験があったのだけど、rubyの場合だとそのようなことはないようだ。

# perl
use strict;
use warnings;

warn rand();

for ( 1..10 ) {
    fork()
        or next;
    warn "$$: " . rand();

    exit;
}

wait() for (1..10);
# ruby
rand()

10.times do 
  Process.fork do
    p [$$, rand()]
  end
end
Process.waitall

実行結果はこのような感じ

$ perl hoge.pl
0.272763864210905 at hoge.pl line 4.
61802: 0.107602762381674 at hoge.pl line 9.
61803: 0.107602762381674 at hoge.pl line 9.
61804: 0.107602762381674 at hoge.pl line 9.
61805: 0.107602762381674 at hoge.pl line 9.
61806: 0.107602762381674 at hoge.pl line 9.
61808: 0.107602762381674 at hoge.pl line 9.
61810: 0.107602762381674 at hoge.pl line 9.
61811: 0.107602762381674 at hoge.pl line 9.
61812: 0.107602762381674 at hoge.pl line 9.
61813: 0.107602762381674 at hoge.pl line 9.

$ ruby hoge.rb
[62124, 0.20627116612114527]
[62125, 0.333383116761385]
[62126, 0.23762467129788523]
[62127, 0.14602078653671557]
[62128, 0.5977205787316395]
[62129, 0.4148314943310857]
[62130, 0.05280638343461841]
[62131, 0.008602114330672928]
[62132, 0.3500050758109612]
[62133, 0.6088914568482737]

fluentdのログを調査するためのツールflgrepを書きました

おかげさまでRack::CommonLogger::Fluentを運用しはじめました。
Mongoに記録してごにょごにょとか、色々な方法があるらしいのですが、とりあえずファイルに記録しておいて、用途はあとで考えようという感じで使っています。

ということで、ログファイルを色々みてみてアクセスがどんな感じなのか、を調べたりするときに便利そうなflgrepというツールを作ってみました。(既にどこかにありそうな気もしつつ。Rack::CommonLogger::Fluentのv0.4.0から同梱されています。

Rack::CommonLogger::Fluentに入ってはいますが、fluentdのログ形式であれば使えるんではないかと思います。

$ flgrep
Usage: flgrep [options]
    -c, --column=s                   column name to target
    -v, --value=s                    value for filtering
    -d, --date[=s]                   filtering by date
        --until[=s]                  filtering by date until ...
        --since[=s]                  filtering by date since ...
    -m, --method=(regexp|gt|lt)      regexp (default): match by regexp, gt: greater than, lt: less than
        --mode=(filter|color)        filter (default): like grep, color: show colored line when match condition.

# 一秒以上かかったアクセスログを調べる
$ tail -f access.log | flgrep --column=runtime --method=gt --value=1.0

# 2012/06/06の20時以降のトップページへのアクセスを調べる
$ tail -f access.log | flgrep --column=path_info  --value=^/$ --since=2012-06-06T20:00