https://github.com/tategakibunko/ocorm
ocormが提供するMakeRelationMapファンクターの定義はこのようなものです。
module MakeRelationMap (F:FieldMap)(S:Schema):RelationMap with type t = F.t
このファンクターは、スキーマの定義モジュール(Schema)と、データ型の変換モジュール(FieldMap)を受け取り、次のことをするモジュールを生成します。
- (string, string) listを、FieldMapが定義するオブジェクトに変換
- (string, string) listを、Schemaが定義する制約に基づいてバリデーション
Schemaモジュールの定義
こんな風に定義します。
module User = struct let table_name = "user" let props = [ ("id", PrimaryKeyProperty); ("name", StringProperty [NotNull; MinLength 3; MaxLength 16]); ("point", IntProperty [NotNull; Default (`Int 0)]); ] end
フィールド名と属性のセットをpropsにセット。属性定義には、制約を付けることができます。
FieldMapモジュールの定義
例としてjson-wheelライブラリの定義するJson_type.t
に変換する処理を定義してみます。
module JsonFieldMap = struct type t = Json_type.t let null = Json_type.Null let map_value prop value = match prop with | PrimaryKeyProperty -> Json_type.Int (int_of_string value) | StringProperty _ -> Json_type.String value | TextProperty _ -> Json_type.String value | IntProperty _ -> Json_type.Int (int_of_string value) | FloatProperty _ -> Json_type.Float (float_of_string value) | BooleanProperty _ -> Json_type.Bool (value = "t") let map_alist alist = Json_type.Object alist let map_list lst = Json_type.Array lst end
null/int/string/float/list/alistといった属性を、自分が変換したいオブジェクトにマッピングする処理を記述します(alistは属性リスト)。
ちなみに上の変換処理ですが、本当はもう少し厳密に変換したほうが良いと思います(空文字だったらどうする?とか)。
MakeRelationMapファンクター
これら二つのモジュールをMakeRelationMapに渡すと、UserテーブルをJSONに変換したり、バリデーションしたりできるモジュールが手に入ります。
module UserMap = MakeRelationMap(JsonFieldMap)(User)
オブジェクト変換
JSONに変換するには、UserMap.objectifyを使います。
let table_row = [("id", "1"); ("name", "taro"); ("point", "100")] in let json = UserMap.objectify table_row in (** Json_type.t が取得できる *) Json_io.string_of_json json => {"id":1, "name":"taro", "point":100}
バリデーション
データをバリデーションするには、UserMap.validateを使います。
(** nameには3文字以上、16文字以下の制約が付いている *) UserMap.validate [("id", "1"); ("name", "a"); ("point", "100")] => exception: name is too short(min 3, max 16)
jingooオブジェクトを出力させる
schemaの定義と変換のモジュールが別々に定義できるので、例えばjingooのテンプレートに渡せるオブジェクトを作りたい場合は、JsonFieldMapモジュールを、こんな感じのモジュールに差し替えるだけで対応できます。
open Jg_types module JingooFieldMap = struct type t = tvalue let null = Tnull let map_value property value = match property with | PrimaryKeyProperty -> Tint (int_of_string value) | StringProperty _ -> Tstr value | TextProperty _ -> Tstr value | IntProperty _ -> Tint (int_of_string value) | FloatProperty _ -> Tfloat (float_of_string value) | BooleanProperty _ -> Tbool (value="t") let map_alist alist = let hash = Hashtbl.create (List.length alist) in Thash (List.fold_left (fun h (n,v) -> Hashtbl.add h n v; h) hash alist) let map_list rows = Tlist rows end (** jingooのオブジェクトに変換するモジュール *) module UserMap2 = MakeRelationMap(JingooFieldMap)(User)
ORM……なのか?
……ご覧の通り、ORMといってもActiveRecordみたいにSQLに関わる部分まで抽象化しているわけではなく、あくまでSQL呼び出しの結果が(string, string) list で取得できたとして、それを特定のオブジェクトに変換したり、ついでにバリデーションしたりっていう仕事をするためのライブラリです。