2009年12月27日日曜日
数値のinList
2009年12月15日火曜日
STS+Snow Leopard
現時点では STS がいちばんまともなGrails IDEなわけだが, Snow Leopardにすると Java1.6がデフォルトで, これはfile.encodingがSJISなのである (Java1.5は多分utf-8だった).
普通のエディタとかはeclipseの環境設定すれば良いだけだが, バージョン管理のコメントとかはSJISになってしまって, 例えばhgに断られる (HGENCODINGMODEをreplaceとかignoreにすれば断られはしないけど, 当然化ける).
仕方がないので, STS.app/Contents/MacOS/STS.ini に-Dfile.encoding=utf-8 という一行を足す.
あまり麗しくないけどしようがない. IntelliJ IDEAとかでも同じじゃないかな, どうかな.
それとは別にmercurialの方でもHGENCODINGMODEをreplaceにしておかないと, コマンド・ラインからもコメントを入れられなかったけど, 本当のところはどうなのか, 分かっていない.
2009年12月4日金曜日
spockを使う
さて, 普通にテストを書いていてもつまらないので:-) ちょっと違うことをやってみよう.
というか, JUnit系を普通に使っているだけだと, 昔ながらの「テスト要件に従った闇雲なテスト」をちょっと前倒しでやっているだけの話になりがちで, テスト書いている人は退屈だし, バグは早めに取れるけど, それだけの話しで, エキストリームじゃない.
もともとKent Beckが言っていたTDDは, 実は「テストの前倒し」とはだいぶ違って, そもそもテストですらなくて (結果的にテストっぽくなってるけど), 設計ingなわけだ. でも「アジャイルやってる」と主張する大方のプロジェクトで, そういうふうにテストを使っているところはあまり見ない. そもそもGrailsのようなDSLによる宣言的記述を中心にした環境を使っていると, そういう出番は限られる.
テストのもうひとつの視点は仕様としてのテストで, これは振る舞い駆動開発, BDDなどと呼ばれている. 今の仕事で, だらだらとした日本語の仕様書を書くのもあほらしいので, 仕様としてのテストを書いている. 動く仕様だ (ただしインスタンス・レベルで). 使うのは Spock. GroovyのBDD環境は, 他に easyB があるけど, いまいちである. テストが読めない. それは仕様書としてはだめだよね. Spockはなかなかいい.
GrailsでSpockを使うにはまず
$ grails install-plugin spock
すればいい.
http://grails.org/plugin/spock には "0.3-SNAPSHOT" とバージョンを明示してプラグインをインストールしろ, と書いてあるけど, 現時点ではバージョンを指定しなければ0.3がインストールされて, それでOK.
とりあえず最低限のドキュメントを訳した (というか, 現状はほぼこれくらいしかない).
2009年8月11日火曜日
Autobase
Autobase pluginはLiquiBaseのGrails wrapperで, LiquiBaseはRuby on RailsのMigrationぽいDBリファクタリング・ツールなんだけど, Autobaseが動かないんだな.
プロジェクトではこれはあるといいと思って評価したいんだけど, うちだけではなく, (作者の環境以外は:-) どこでも動かないらしい. 作者は忙しくて手を付けられないみたい.
かといって, XMLの塊であるLiquibaseを直接使うつもりはない (きっぱり). XMLを扱うつもりでよければ, Grailsで直接使うためのプラグイン (LiquiBase Database Refactoring for Grails) もあるけど.
# scriptsからは, srcもgrails-app/utilsも見えないのだよな... ところがたまに見えることがある...
2009年8月10日月曜日
Hudsonを動かしてみたよ
https://hudson.dev.java.net/ に行って, 最新版をダウンロードする. MacOSXならば, ダウンロードした hudson.war をダブル・クリックするだけ. http://localhost:8080/ をブラウズすると, すでにHudsonは立ち上がっている.
「Hudsonの管理」メニューから「プラグインの管理」に行って, 例えばGrails, Groovy, Mercurialの各プラグインをインストールしてみる.
「Hudsonの管理」メニューから「システムの設定」に行って, Grails Builderに「追加」して, 適当な名前 (grails-1.1.1) と GRAILS_HOME 環境変数と同じ値を設定する. で, 保存. 「Hudsonの管理」メニューから「設定の再読み込み」をした方がいいみたい.
ダッシュボードに戻り, 適当に名前を付けてジョブをひとつ作る. ソースコード管理システムはMercurialなので, そのプロジェクトのローカルなパスをRepository URLに指定する.
「ビルド手順の追加」して, Build with Grails する. Grails Iinstallation はさっき Grails BUilder で指定したやつ (grails-1.1.1) を, Targetsには "test-app --non-interactive" とか書いてみる.
「ビルド実行」すると...
Compilation error: srcdir "/Users/masaki/.hudson/jobs/AutobaseExample/workspace/src/groovy" does not exist!
とか言ってfailしているよ!
buildに先だって, hgのリポジトリからcloneでソースコードを取り出しているのだが, その時に空のディレクトリは取り出されないのが原因のようだ...
これはmercurialがそういうものだから仕方ない. で, 次のようなgantスクリプトを作って, <basedir>/scriptsに置き, 手で % grails unemptyDirectories することにする. grailsの方を弄ってもいいのだったら, <grails>/scriptsに置いて, scripts/_GrailsCreateProject.groovyのcreateAppターゲットの最後のeventの前に呼ぶようにしてもいいかも知れない.
% cat scripts/unemptyDirectories.groovy
target ('default': "Makes a hidden file ('...') under the empty directories for marcurial+hudson") {
new File("${basedir}").eachDirRecurse {
if (it.list().length == 0) ant.touch file:"${it}/..."
}}
もっとも例えばNetBeansからaddすると'.'ファイルは無視されてしまうから, hgで直接addする必要があるのだが...
というわけで, 今の自分の環境とは合わないけど, Hudson自身はよさそう.
2009年8月5日水曜日
Grailsのテスト・ツール
Grailsのようにアプリケーションを作るのが, 簡単, というか仕様を書けばいいだけ, になってくると, 相対的にテストの比重がどんどん大きくなってくる. となると考えるのが, テストをどうやって楽にするか, だ.
Grailsのテスト・ツール, 特にテスト・データ管理のプラグインを簡単にまとめておく.
DBUnit Plugin
Javaのデータベース用テスト・ツール, DBUnit をそのままプラグイン化したもの.
http://code.google.com/p/dbunit-plugin/ (コード)
http://grails.org/plugin/dbunit
基本的には, DBUnitそのものだから, XMLでデータセットを <PROJECT>/test/datasets に定義して
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<person id="1" name="Pessoa 1" email="p1@email.com" version="1" />
<person id="2" name="Pessoa 2" email="p2@email.com" version="1" />
<person id="3" name="Pessoa 3" email="p3@email.com" version="1" />
<person id="4" name="Pessoa 4" email="p4@email.com" version="1" />
<person id="5" name="Pessoa 5" email="p5@email.com" version="1" />
</dataset>
テスト・コードの中でそれを呼び出すことができる.
static datasets = ['person']
GrailsでXMLかよ, というツッコミは当然だろう. そこで,
Fixtures Plugin
簡単にいえば, データセットをDSLで記述できるようにしたもの.
http://www.grails.org/plugin/fixtures
例えば, 次のようにデータセットを定義して (<PROJECT>/fixtures/foo.groovy),
fixture {
guillaume(Author) {
name = "Guillaume Laforge"
}
dierk(Author) {
name = "Dierk Koenig"
}
gina(Book) {
title = "Groovy In Action"
authors = [guillaume, dierk]
}
}
あるいは
fixture {
guillaume(Author, name: "Guillaume Laforge")
dierk(Author, name: "Dierk Koenig")
gina(Book, title: "Groovy In Action", authors: [guillaume, dierk])
}
テスト・コードの中で, それを呼び出すことができる.
def fixtureLoader
fixtureLoader.load("gina")
Build Test Data Plugin
前の二つが, データセットをすべて, 自分の責任で用意するタイプだった (だから手間はそれなりに掛かるけど, 詳細まで指定できる) のに較べて, このプラグインはドメインクラス定義の中で指定したconstraints に合わせて「適当に」データセットを作ってくれる.
http://www.grails.org/plugin/build-test-data
http://bitbucket.org/tednaleid/grails-test-data/wiki/Home (ドキュメント)
使い方はきわめて簡単で
def a = Author.build(firstName: "Daniel")
これだけ. 指定したプロパティ以外は「適当に」(関連も含めて) 作ってくれる.
ちょっとしたテストにはこっちの方が便利だろう.
この先
とは言え, テスト用のデータセット管理/生成ツールをもう少し何とかしたいという気もするし, Grailsならば何とかなるだろうという気もする.
アイデアはあるけど, 実装している時間が無い.
2009年7月5日日曜日
criteriaでのプロパティ指定
Grailsのユーザ・ガイドのCriteriaの項を読んでいて, こんな例が出ているので,
def criteria = Task.createCriteria()
def tasks = criteria.list{
eq "assignee.id", task.assignee.id
join 'assignee'
join 'project'
order 'priority', 'asc'
}
「ふーん, プロパティ名には'.'を含むナビゲーションが書けるのね」と軽く読み飛ばしていたのだが, それは一般には無理で, 'id'の場合のみ許されるようなのだ, と言うことが分かってきた.
じゃあ, それ以外の「行った先の」プロパティでクライテリアを組み立てたい場合にはどうすればいいか.
eq "assignee", Person.findByName("foo")
なんてのが一つ. でもこれはid的でないプロパティでは使いにくいだろう.
assignee { eq "name", "foo" }
でもOKぽい (つまり関連先が多でなくてもクライテリアには書ける).
単純なことだが, 備忘のために記しておく.
2009年7月1日水曜日
simple groovy code reading - MOP
MOP, Meta-object Protocolとは, プログラム自身をプログラミングの対象として扱う時のAPIです. もっとおおざっぱに言うと, Javaのリフレクションをもっと使いやすく, 柔軟に, 強力に, 綺麗にしたようなものだと思えばいいでしょう. ある意味ではいわゆるアスペクト指向プログラミングもここに含まれます (それをもっと汎用的にしたものです).
GroovyのMOPはいちばん基底では, インタフェイスgroovy.lang.MetaObjectProtocolで規定されています.
- MetaClass extends MetaObjectProtocol
- MutableMetaClass extends MetaClass
- MetaClassImpl implements MutableMetaClass
- ExpandoMetaClass extends MetaClassImpl
- ProxyMetaClass extends MetaClassImpl
- MockProxyMetaClass extends ProxyMetaClass
- ClosureMetaClass extends MetaClassImpl
1から3までが普通のGroovyObjectのメタクラスを表しています. 1,2 はインタフェイスで, 3はクラスです.
4から7はちょっと特殊なメタクラスです. メタクラスのレベルでいくらでもぐりぐりいじれちゃう, みたいなメタクラスも含まれています.
MOPを簡単に見てみると,
- properties - プロパティの一覧
- methods - メソッドの一覧
- respondsTo - あるメソッド名+引数のメッセージを受け付けるかどうか
- hasProperty - あるプロパティを持っているかどうか
- invokeMethod - あるメソッド名+引数でメソッドを起動
- getProperty/setProperty - ある名前のプロパティにアクセス
- addInstanceMethod - メソッドの追加
おおざっぱにはこんなAPIを用意していれば, メタレベルで如何様に操れる, と言うことです. 言い換えればオブジェクトの (実行時の) 本質はたかだかこんなものだとも言えます. つまり, オブジェクトとはMapにメッセージを受け付ける機構, 受け付けたメッセージからメソッドを探索する機構, 探索したメソッドを起動する機構を付け加えたものなのです.
今日はここまで.
simple groovy code reading - closure
Groovy最大の特徴のひとつであるクロージャは, 謎でも何でもなく単なるClosureクラスのインスタンスです. なので, クロージャが何かはgroovy.lang.Closureを見てみるのがいちばんです.
Closureでいちばん目立つのは, call()とcurry()です. call()は名前のまま, そのクロージャに引数を与えて実行します. 単にmetaClassのinvokeMethodを呼んでいるだけですね.
curry()は引数の一部を固定したクロージャを作るものですが, その通り, 引数の一部を値として持つサブクラスCurriedClosureを作って返しているだけです. 実際に呼び出すときには, この固定した値をもとのClosureの引数として与えなければならないのですが, それはinvokeMethodを実装したMetaClassImplの中で行われています.
その他にも妙なものがありますよ. resolveStrategyですね. 値は
- DELEGATE_FIRST
- DELEGATE_ONLY
- OWNER_FIRST
- OWNER_ONLY
- TO_SELF
の5種類です.
Closureはownerとdelegateの二つのプロパティを持っています. ownerはそのClosureを定義したオブジェクトです. delegateは, クロージャが自分で解決できないプロパティを参照したときに, resolveStrategyに従って探しに行くオブジェクトです. デフォルトではdelegateはownerと同じであり, resolveStrategyはOWNER_FIRSTです. したがって
class Foo { def a = 1; def aa = { println a}; def bb = { println b } }
foo = new Foo()
とすると foo().aa() は 1を出力しますが, 当然 foo().bb() はエラーになります.
ownerは変えることができませんが, delegateは変えることができますから,
class Bar { def a = 100; def b = 2 }
foo.bb.delegate = new Bar()
foo.aa.delegate = new Bar()
とすると
foo.bb()はdelegateのBarからbを探して2を出力しますが, foo.aa() は 1のままです.
ここで
foo.aa.resolveStrategy = Closure.DELEGATE_FIRST
すると, foo.aa()も先にBarからaを見つけて100を出力するようになります.
さて, このdelegateが何に役立つかというと, DSLを定義するのに便利なBuilderを作るのに役立っていたりするのです...
simple groovy code reading - operators
Groovyには, Javaに較べて多くの演算子があります. またJavaでは単なる構文要素であるものが, 演算子として再定義できたりします. それらはとても便利なのですが,
- どんな演算子が使えるか
- 使えたとして優先順位はどうなのか
- 多重定義する時のメソッド名は
など, なかなかまとまって調べられる場所もないのです...
そういうときには org.codehaus.groovy.syntax.Types を見てみましょう (えーと, あまり本気にしない方がいいかも知れませんよ:-) 特によい子は真似しないように).
ここには演算子のシンボル定義や優先度が書かれています.
だからてっきり, is()に相当する "===" (昔はあった) が使えるものかと思ってやってみると...
groovy:000> 1 === 1
ERROR org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed, groovysh_parse: 1: unexpected token: = @ line 1, column 5.
1 === 1
^
1 error
at java_lang_Runnable$run.call (Unknown Source)
駄目っぽいですね(^_^;)
コードの一部だけ見て, 信用しちゃいけないって, 教訓です.
simple groovy code reading - null
Groovyでの null (ナル) オブジェクトは, org.codehaus.groovy.runtime.NullObjectの (唯一の) インスタンスです.
で, NullObjectの中身を見てみると, Object invokeMethod(String name, Object args) の中で,
throw new NullPointerException("Cannot invoke method " + name + "() on null object");
しています.
これこそ我々がしょっちゅう見る, あの「文字列」の本体なのですね.
でもさ, nullにメッセージを送ったらこんな文字列が返ってくるなんて不合理だと思いませんか? オブジェクト指向以前の「関数呼び出し」の臭いが芬々と漂っているようです.
nullに何かを依頼したら, 無視して欲しい (つまり単にnullを返して欲しい) ... と自分は強く思うわけです. Objective-Cの実装はそうなっていたと思います.
# まぁそのために?.演算子があるわけですが:-)
というわけで, NullObjectのinvokeMethodを上書きしたいと思うのですが...
まずはmixinを使ってみましょうか.
class NullMixin { Object invokeMethod(String name, Object args) { return null; } }
org.codehaus.groovy.runtime.NullObject.mixin(NullMixin)
だめですねぇ. では次に, ExpandoMetaClass (EMC) を.
org.codehaus.groovy.runtime.NullObject.metaClass.invokeMethod = { String name, Object args -> null }
やっぱだめでした...
simple groovy code reading - Object methods
Javaでは, Objectに定義されているメソッドは, ごく少数で基本的なものに限られています.
- clone
- eqauls
- finalize
- getClass
- hashCode
- notify, notifyAll
- toString
- wait
それに対して, Groovyでは, お馴染みprintln始め, 非常に多くのメソッドを使うことができます.
Javaでは次のように, きわめてトップダウンかつアドホックな思考パタンになっているわけです.
「printlnの責務を持つのは当然出力を担当するやつ (PrintStream) だよな. printlnしたければPrintStreamを出力先を指定してnewして, それにprintlnすればよかろう」
「ま, しかしprintlnなんてコードのどこからでもしたいだろうし, 出力先はほとんどは標準出力だろうから, ちょっとだけ便利にしてやろう. 標準出力先としてoutというインスタンスを用意してやろう」
「しかも特定のインスタンスにどこからでもアクセスするためのJavaのやり方はstaticメンバにするのが当たり前だから, そのためにSystemというクラスも作っといてやろう」
そのお陰で我々は"System.out."というあまり意味のない枕詞をそこら中に書かされる破目になっています.
「あるオブジェクトの中でやりたいことがある. としたら, それはそのオブジェクトに任せるのが筋だ. どんなオブジェクトでもやりそうなことならば, それはObjectに書いておけばよい. もちろん実装では, 最終的に出力を担当するやつにやらせることになるだろうが, それはここに書くべきことじゃない. それに'System'なんて訳分からんわ」
と言うわけで, GroovyではprintlnはObjectに実装されているのです.
では, Objectには他にどんなメソッドが追加されているか, 見てみることにしましょう.
それがorg.codehaus.groovy.runtime.DefaultGroovyMethodsです. ここにはおよそ700個以上のメソッドが定義されています. ただし, これらのメソッドの第一引数の型が追加対象の型を表しますから, すべてがObjectのメソッドになるわけではありません. Javaの既存の型 (コレクションやString, Streamなど) に追加したいメソッドがここに集結しています.
中でもObjectに追加される整理して挙げておきます.
- foo.is(bar) - Javaでいう foo == bar
- foo.with { a = 1; b = 2 } - 自分をdelegateとしてクロージャを呼ぶ
- getAt(), putAt() - オブジェクトをあたかもMapのように扱う仕組み. 例えば foo[a] として, プロパティaにアクセスできます
- dump() - オブジェクト情報を書き出します. toString()を一生懸命書かなくてもいいかも知れません. "foo".dump() ===> <java.lang.String@18cc6 value=foo offset=0 count=3 hash=101574>
- metaPropertyValues - これは直接はあまり使わない. 次のgetPeopertiesを使う
- foo.properties - プロパティとその値の一覧. "foo".properties ===> {class=class java.lang.String, bytes=[B@2e2167}
- use - ご存知カテゴリ
- mixin - Foo.mixin(Bar) とするとBarのメソッドをFooに追加します. コードを見ると, Foo.mixin([Bar, Baz]) とかできるのが分かります
- addShutdownHook {...} - 名前のとおり
- print系
- invokeMethod("foo", 1) - this.foo(1)
- isCase(bar) - ご存知switch/caseで使われるものですね
- コレクション系 - find, eachなどコレクションで使われるメソッド群
- metaClass - メタクラスへのアクセス
- 型変換系
なかなか面白いでしょ?
不思議なのはコレクション系ですね. 例えばListにeachがあるのは当然ですが ([1,2,3].each { println it } ), なぜObjectにまでeachがあるのでしょう?
その理由は「普通の単一のオブジェクトもひとつの要素からなるコレクション (singleton) として扱いたい」からです. だってそうなれば, 普通のオブジェクトとコレクションを区別しなくて済みますよね. Listを返すべきところで, 要素がひとつしか無ければ, JavaではそれをListで包んで返す訳ですが, Groovyならばそのまま返してもいい, ということです (そういうのを嫌がる人も多いとは思いますけどね:-)
このruntimeパッケージには他にも面白いクラスがいっぱいあります.
simple groovy code reading - import
Javaではjava.langだけはimportしなくても使えることになっています (デフォルト・パッケージ) が, それ以外はいっぱいimportしなければなりません. 特定の機能を実現するパッケージをimportで指定するのはいいとして, java.utilのような基本的な機能までいちいちimportしなければならないのは, 納得がいきませんよね.
Groovyでは
- java.util.*
- java.io.*
- java.net.*
- groovy.lang.*
- groovy.util.*
- BigInteger
- BigDecimal
がデフォルトでimportされます. これはもちろんスクリプト言語として, 普通に使うものはimportせずに済むようになっているわけです.
では, simple groovy code readingの一発目として, どこでこれらがimportされているのか調べてみると...
パッケージに関しては, org.codehaus.groovy.control.ResolveVisitorにDEFAULT_IMPORTSとして定義されています. さらにこれがどこで使われているかを見ると, 同じクラスのresolveFromDefaultImports()で, 具体的なクラス・ノードがどのデフォルト・パッケージに含まれているかを調べています. BigInteger, BigDecimalについてもここで扱っています.
DEFAULT_IMPORTSはpublicなので外から見ることができます. ただしfinalなので, デフォルト・パッケージを勝手に追加することは, 残念ながらできません:-)
2009年6月23日火曜日
WebFlowTestCaseが変わったらしい
どうやらgrails-1.1からWebFlowTestCaseが変わったらしい. なので「Grails徹底入門」のWebFlowのテストはもう古い.
でも grails.orgの記述もアップデートされてない! 1.1.1のオンラインのuser guideのみ, こそこそ消してあるぜ:-)
新しいWebFlowTestCaseは次のようになっています.
startFlow()
対象flowの最初の状態をトリガする. 前はこれでViewSelectionが戻っていたけど, 今はvoid.
signalEvent(String)
対象flowにイベントを送る. startFlowと同様, 今やViewSelectionは返されない (ExternalContextを返す).
getFlowDefinition()
対象flowの定義を得る.
setCurrentState(String)
新しいflowを開始し, 指定した状態に遷移する.
getFlowScope()
現在のflow scopeを得る.
getConversationScope()
現在のconversation scopeを得る.
ViewSelectionを直接扱う代わりに, assertXXXの中に隠蔽されています.
assertCurrentStateEquals(String)
flowが実行中で, 現在の状態の名前が引数に等しい.
assertFlowExecutionActive()
flowが実行中 (まだ end 状態に達していない).
assertFlowExecutionEnded()
flowが終了 (既に end 状態に達している).
assertFlowExecutionOutcomeEquals(String)
flowが終了しており, その end 状態の名前が引数に等しい.
新しいWebFlowTestCase APIに関しては, Grails in Actionの9.4が詳しい.