ずっと表題のようなものが欲しいと思って、例によって誰かが作ってくれるのを待っていたのですが、そういう気配がなさそうなので、自分専用の仕様で、それっぽいものを作ってみました。
バリデーションの処理をもうちょっと丁寧に書けたら、縦書き文庫にも導入しようかなと思っています。
ずっとViewとControllerまでは分離できていても、Modelが分離できていないのが気持ち悪かったのですけど、これでソース内のSQLがちょっとは減るかな・・・
アイデアとしては、ファンクターのシグニチャにテーブル定義を渡し、それぞれのモデルのモジュールを作成する、という方法にしました。
行数は少しだけ膨らんで280行程度になりましたが、まだJOINがらみを入れたいので、もうちょっと膨らみそうです。その辺がまとまったら独立したライブラリとして公開できるかも(つっても大したものじゃないんですが)。
今のところ、使い方はこういう感じになっています。
open Or_mapper module UserTable = TableMapper (struct let name = "user" let props = [ ("id", StringProperty("ユーザーID", 16, [NotNull; StringUserId])); ("name", StringProperty("ユーザー名", 16, [StringRange(1,8)])); ("karma", IntProperty("カルマ値", [IntDefault 0])); ("user_id_pk", PrimaryKeyProperty); ] end) module NovelTable = TableMapper (struct let name = "novel" let props = [ ("id", SerialProperty [NotNull]); ("title", StringProperty("作品タイトル", 16, [StringRange(1,16)])); ("char_count", IntProperty ("作品字数", [IntRange(1, 30000)])); ("novel_id_pk", PrimaryKeyProperty); ] end) let (@@) f x = f x let () = print_endline @@ UserTable.create (); print_endline @@ NovelTable.create (); print_endline @@ UserTable.insert [("id", Dval.string "aa"); ("name", Dval.string "hoge"); ("karma", Dval.int 10)]; print_endline @@ UserTable.update ~where:["id = 'aa'"; "karma > 0"] [("name", Dval.string "hoge")]; print_endline @@ UserTable.select ["id"; "karma"] ~where:["karma>10"] ~limit:(Some 10) ~offset:(Some 40)
現在は実装上の思いつきをテストしている段階なので、DBアクセスはせずにジェネレートされたSQLを出力しているだけですが、処理の内部で型チェックと入力のバリデーションをライブラリに任せることが出来たので、個人的には殆ど目的を達成した気分になっています。
あとは結果をどこまで正確にラップするようにするか、型情報も含めて正確にデータセットのラッパーを作るかどうかですが、僕としては入力データのバリデーションだけ自動化できたら満足なので、結果は string list list(あるいは (string*string) list list) とかでもいいかなと思っています。
欲を言えば、実行時じゃなく、モジュール利用の記述時、すなわちコンパイル時にタイプエラーを発見できるように、現在のモジュール定義から、より厳しい型付けのモジュール定義のソースをジェネレートする段階を作ったらもっといいのでしょうけど。
さておき、まずはJOIN のサポートかな・・・