Tuesday, September 1, 2009

routes_cov to reveal untested actions


I'v released a new rails plugin, routes_cov. This plugin tells you whether untested controller action there or not against routes. I'll be happy if you think it it useful.

routes_cov というRailsプラグインを作ってみました。テストで実際に実行されたアクションと routes (rake routes で表示されるやつ) を比較してテストされてないアクションを列挙します。まだまだ改善の余地はありますが、使ってみて下さい。



$ rake test:functionals
....
Finished in 13.932317 seconds.

121 tests, 307 assertions, 0 failures, 0 errors

There are unperformed actions here:
groups#index
groups#show
users#member
...

Monday, August 17, 2009

port install subversion+unicode_path

git-svn を試してみようと思って macports で git-core +svn をインストールするときに、何気に subversion port の info を見たら unicode_path という variant があるじゃないですか。
パッチファイルのコードを見るとなんだか見覚えがあります。
例の subversion の issue のコメント (by Philip Kime)を見るとやっぱり僕のパッチがもとになってるようです。

というわけで、前回の記事で紹介した方法のように面倒なことしなくても、

$ sudo port install subversion +unicode_path

で OK です。MacPorts 万歳。

ちなみに、 +unicode_path のパッチファイルは以下のパスにあるハズです。

/opt/local/var/macports/sources/rsync.macports.org/release/ports/devel/subversion/files/patch-path.c.diff

ソースコードコメントのインチキな英語もそのままだ...。

Friday, August 7, 2009

Cutting a git commit off a branch, and pasting it on another

git のトピックブランチでの作業中にバグ修正をコミットしたんですけど、そいつをリリースしようとしたときに、まだリリースできない変更を含むトピックブランチにコミットしてたことに気づくことがあります。
Subversion ならトピックブランチのコミット済みの履歴をもとに戻すことできないので、そのコミットのリビジョンだけを trunk にマージしますね。
Git でも同様にその変更をだけをマージすれば簡単なんですけど、 git の rebase とかでトピックブランチへのコミットを無かった事にして、 master に移動できるのではないかと考え頑張ってみました。

作業前の状態はこんな感じです。 i18n ブランチの b1ad369 が移動したい commit です。

$ git log --oneline -5 i18n
431120e localize filters#new in Japaense
b1ad369 handling EOFError in Feed.open()
4d4dac5 Japanese localizing top page
c04524f I18n support
2688536 Down rails version form 2.3.3 because of Hoptoad doesn't work.

"git rebase --onto newbase upstream [branch]" の構文に当てはめると、

$ git rebase --onto 4d4dac5 b1ad369 i18n

これで、バグ修正のコミットは i18n ブランチから削除されました。
$ git log --oneline -4 i18n
51a74d5 localize filters#new in Japaense
4d4dac5 Japanese localizing top page
c04524f I18n support
2688536 Down rails version form 2.3.3 because of Hoptoad doesn't work.

ここで、注意するべきは b1ad369 のあとの commit のハッシュが 431120e から 51a74d5 に変更されていることです。歴史を変えたのでタイムパラドックスが発生、同じように見える現在が微妙に変わる感じですかね。また、 b1ad369 は i18n ブランチからは削除されましたが、 commit は存在しています。

$ git log --oneline -4 b1ad369
b1ad369 handling EOFError in Feed.open()
4d4dac5 Japanese localizing top page
c04524f I18n support
2688536 Down rails version form 2.3.3 because of Hoptoad doesn't work

ちなみに、このケースでは master ブランチには i18n が分岐してからは何もコミットされていないので、

$ git log --oneline -1 master
2688536 Down rails version form 2.3.3 because of Hoptoad doesn't work.

i18n ブランチの履歴にも登場した 2688536 が master HEAD になります。
ここから commit b1ad369 をマージするためにはこの無名ブランチから b1ad369 と 2688536 の間2つ commit を削除しなければならないかと思ったのですが、 cherry-pick (つまみ食い) という便利なコマンドで任意の commit の変更だけマージできます。

$ git checkout master
$ git cherry-pick b1ad369

これで期待通りの結果になりました。

$ git log --oneline -2 master
92244c5 handling EOFError in Feed.open()
2688536 Down rails version form 2.3.3 because of Hoptoad doesn't work.


2008-8-8: 更新


コメントの指摘により cherry-pick を使うように変更しましたので不要になった部分は以下 display:none してます。

b1ad369 を無名ブランチの HEAD として考えて、 master HEAD との間にある2つの commit を rebase で削除してから master にマージすれば期待している結果になるはずです。(ここで早まってマージするとその要らない2つの commit もマージされてしまいます。)

$ git rebase --onto 2688536 4d4dac5 b1ad369
$ git log --oneline -2
0960a76 handling EOFError in Feed.open()
2688536 Down rails version form 2.3.3 because of Hoptoad doesn't work.

ここでも、過去を変更した以降の commit は別の hash になるのでそれを指定して master にマージ

$ git checkout master
$ git merge 0960a76
$ git log --oneline -2 master
0960a76 handling EOFError in Feed.open()
2688536 Down rails version form 2.3.3 because of Hoptoad doesn't work.

結果は期待通りですが、正直、二度とこんなことしたく無い感じですね。一発でできるコマンドありそう。

Thursday, July 30, 2009

Passenger: Keeping ApplicationSpawner alive speeds up spawning an instance (updated)

前回 passenger プロセスの起動時間について書いたとき passenger のバージョンは 2.0.3 でしたが、あれから 9ヶ月、 2009-07-29 現在の最新バージョンは 2.2.4 になってます。

そのときは passenger の constants.rb を直接変更するという荒技で FrameworkSpawner と ApplicationSpawner のタイムアウト時間を長くしてましたが、いつのまにか RailsFrameworkSpawnerIdleTimeRailsAppSpawnerIdleTime という Apache の設定で変更できるようになってます。こいつらを 0 にすれば FrameworkSpawner は Apache を再起動するまで、 ApplicationSpawner は Apache を再起動するか、 touch restart.txt するまで持続します。

また、前回の記事 では Capistrano でデプロイしたときに (タイムアウトを延ばしているときは特に) ApplicationSpawner を KILL しないと古いデプロイの ApplicationSpawner がずっと残ると書いたんですけど、 2.2.0 の "Support for Capistrano-style deployments" により、 Capistrano 使ってても touch restart.txt で ApplicationSpawner が再起動するので KILL する必要は無くなりました。
(でも、2.2.0 から 2.2.2 までは 2.2.3 の "Fixed restarting of the ApplicationSpawner server" が修正されてなかったので worker process がみんなタイムアウトしている間に restart すると古い ApplicationSpawner が残っちゃってたみたいです。)

ちなみに、Apache 再起動直後の FrameworkSpawner も ApplicationSpawner もいないときと、 worker process だけ fork するときの速度をブラウザのページロード時間で測ると、(僕の環境では)5.6秒が1.0秒になるので、デフォルト設定で FrameworkSpawner がタイムアウトする時間30分に一度アクセスがあるかどうかわからないようなサービスではこの設定は必須かと思われます。

2009-7-30 10:00 追記


結論だけ言うと、 passenger-install-apache2-module を実行した直後に追加する apache の設定に1行追加すればOKです。

PassengerRoot /opt/ruby-enterprise-1.8.6-20090610/lib/ruby/gems/1.8/gems/passenger-2.2.4
PassengerRuby /opt/ruby-enterprise-1.8.6-20090610/bin/ruby
+ RailsAppSpawnerIdleTime 0

ApplicationSpawner が常駐することによるメモリ消費は僕の小さなアプリケーションREE を使っていれば private メモリで 25M 程度でした。これだけのコストでアプリケーションのレスポンスタイムの5秒ぐらいのロスが無くなるなら大歓迎でしょう。 Passenger のデフォルト設定でもいいかと思うぐらい。

Friday, July 24, 2009

a bookmarklet which loaded external script and open in another window


I updated the bookmarklet for FeedKraft. Changes are:
* Now it work even alternate links have relative path (URI).
* Open feedkraft in another window.

To keep maintainability, I decided to split complex part of the scripts into a external js file. However, Safari 4 seems to just ignore window.open() in a external loaded script, and Firefox 3.5 asks users whether popup a window or not.

To get those features work for Safari and Firefox, I struggled with javascript, but I got it.

FeedKraftbookmarklet を更新して
* alternate link の href が相対パス(URL) でも登録できるように
* 別 window で開くように
しました。

"/feed/atom.xml" のようなパスをURLに変換するコードはそれなりに長くなるので bookmarklet の javascript: 以下に続けて書くメンテしずらいので、外部 js ファイルをロードするようにしました。
しかし、外部 js ファイルからでは 別 window で開くための window.open() を実行しても Safari 4 では何も起きないし、 Firefox 3.5 では ポップアップブロックの確認が生じてしまいます。

試行錯誤の末、以下のようなコードになりました(見やすいように空白や改行を入れてます)。



javascript:
void(w = window.open());
void(e = document.createElement('script'));
void(e.src = 'http://feedkraft.com/bookmarklet.js');
void(e.addEventListener('load', function(){ w.location = FeedKraft.bookmarkURL()}, false));
void(document.body.appendChild(e));



Why does window.open() first is to avoid calling it in load event handler which behave like external file (I mean can't open a window). Annoying void() is for Firefox. Without enclosing a sentence with void(), the script will return with result of the sentence.

I'm not a Javascript ninja. I never think that this is the best solution. Please give me advice if you have idea.

load イベントで直接 window.open() すると外部 js ファイルで実行したときと同じ挙動になってしまうため、あらかじめ window を開いてから location を変更してます。
理屈わかってませんが、 void() で囲っていない文があると Firefox ではそこでストップして、その文の結果が表示されてしまいます。

Javascript は苦手なのでもっと良い方法があったら教えて下さい。

Friday, July 17, 2009

FeedKraft prototype version


I just announce that my new application service, FeedKraft, is available. As you notice from the name, it deals RSS and Atom feeds. I said it deals feeds, but for now, there is one function, feed filtering.

As for the filter function, we know there are many other filter services out there. I think FeedKraft is easier to use than Yahoo! Pipes, and little bit more powerful than FeedRinse.

The source code, implemented in Ruby on Rails, is also available here on GitHub.

Anyway, give it a try.


FeedKraft という RSS や Atom フィードを扱うサービスを公開しました。
が、今のところは外部のフィードにフィルターをかける機能しかありません。

フィードのフィルターに関しては、同様の機能を提供するサービスはいくつかありますが、Yahoo Pipe より簡単で、 FeedRinse よりちょっと強力かと思います。

特徴としては、
* 入力フィードを解析してファイルター可能な候補を選択できる (例: このブログの Atom フィードをカテゴリ Rails でフィルター)
* 作成したフィルターは公開され、他のユーザーが作成したフィルターも利用できる
* フィルター結果の feed はログイン状態での購読(subscribe)により生成されるURLにより参照する必要があるので、何人購読しているか分かる

どんだけ意味があるかわかりませんが、フィルター結果などのフィードが公開されることで、フィード提供側と利用側によるコミュニティ的な要素が広がるといいかなと思ったりもしてます。

ちなみに、Rails で実装してます。ソースも GitHub にありますので興味があったらどうぞ。

Thursday, July 16, 2009

A meta data table structure Force.com is using

InfoQ に salesforce.com の Chief Software Architect, Craig Weissman が Force.com の内部設計を語るプレゼンテーション InfoQ: The Internal Design of Force.com’s Multi-Tenant Architecture が上がってました。

こういう巨大なサービスの内部構造には興味があるので見てみました(英語をほとんど聴き取れませんでしたが)。Force.com - Multitenant Architecture Under the Covers « Blog - Dayspring Web Design and Development にも第三者による要約があります。

全体の指針として、
* 1つのバージョンだけで顧客毎のカスタマイズはしない
* 開発者は現在のバージョンと次のバージョンだけの保守で済む
* ソーシャル系サービスと違い、会社などの組織単位でリレーションを分離できるので、 OrgID のハッシュでテーブルを partition

とくに興味があったのはユーザー定義のメタデータをどうやって保持しているかで、
* オーバーヘッドが大きいので DDLを使わない(動的に CREATE/ALTER/DROP TABLE しない)
* 自由度の高いピボットテーブル? でメタデータを定義、保持
* 数値や日付などのデータも文字列カラムで保持

具体的には、プレゼンテーションの 15:00 前後のスライドで解説していますが、Fields, Objects, Data の3つのテーブルを使っています。

ちゃんと理解してるか自信ないんですけど、たとえば、

Person:
Name: Hiroshi Saito
BirthDate: 1973-09-17

という構造をつくる場合、


Objeject:
ObjID: 1
ObjName: Person

Field:
FieldID: 1
ObjID: 1
FieldName: Name
DataType: String
FieldNum: 0

Field:
FieldID: 1
ObjID: 1
FieldName: BirthDate
DataType: Date
FieldNum: 1

Data:
GUID: 1619c672-71d2-11de-8643-e784bb274b72
ObjID: 1
Name: ???
Value0: Hiroshi Saito
Value1: 1973-09-17


Person は ObjID=1 とわかっていれば、 Person は以下のクエリで検索できますね。

SELECT Value0 AS Name, Value1 AS BirthDate FROM Data WHERE ObjID = 1;


Index のあたりの話などは理解してませんが、 SalesForce が実際に運用しているデータ構造ならば自分で思いついたのよりまともなハズなので、次回、ユーザー定義データを保存する必要がある場合はこの方法でやってみようかと思います。