anti scroll

ブラウザと小説の新しい関係を模索する

Ocaml によるO/R mapper

 ずっと表題のようなものが欲しいと思って、例によって誰かが作ってくれるのを待っていたのですが、そういう気配がなさそうなので、自分専用の仕様で、それっぽいものを作ってみました。

 バリデーションの処理をもうちょっと丁寧に書けたら、縦書き文庫にも導入しようかなと思っています。

 ずっと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 のサポートかな・・・