Ruby Freaks Lounge

第14回DataMapperの使い

前回に続いて、今回はMerbで主要なORMとして使われているDataMapperを紹介します。

DataMapperは、Patterns of Enterprise Application Archtecture⁠通称PofEAA)で紹介されている「データソースのアーキテクチャに関するパターン」の一つです。他にも、Railsでお馴染みのActiveRecord、テーブルデータゲートウェイ、行データゲートウェイなどが紹介されています。

DataMapperは、その名の通りPofEAAのDataMapperパターンを実装するORMとなっています。

DataMapperパターンとは

DataMapperパターンは、モデルとデータベースの間を取りなすMapperという中間的な構造を持っています図1⁠。

図1 DataMapperパターン
図1 DataMapperパターン

そのため、ActiveRecordよりも抽象度が高く、様々なデータソースを透過的に扱う事ができます。例えば、DataMapperでCouchDB を使うためのアダプタ(dm-couchdb-adapter⁠⁠、Google App EngineのDataStoreを利用するアダプタ(dm-datastore-adapter⁠⁠、REST APIを公開するWebサービスをストレージとして利用するアダプタ(dm-rest-adapter)などが存在しています。

DataMapperの仕組み

DataMapperのコア部分(dm-core)は、主に表1のような構成要素からなります。

表1 DataMapperの構成要素
構成要素役割
DataMapper::Resourceレコードの実装を提供する
DataMapper::Modelモデルクラスの実装を提供する
DataMapper::Queryデータストアへのリクエストを表す
DataMapper::AbstractAdapterデータストアを抽象化する
DataMapper::Collectionクエリの結果を表す
DataMapper::Associationsモデル間の関係を表す
DataMapper::Propertyモデルの属性を表す
DataMapper::RepositoryMapperを表す

DataMapperのコンポーネントは、基本的にクラスではなくモジュールとして提供されているため、アプリケーションで利用するモデルクラスにインクルードして使います。

Resourceモジュールをインクルードすると、自動的にModelモジュールがextendされ、PersonクラスはModelクラスとして振る舞うようになります。これは図1のPersonに相当します。

図1のPerson Mapperに相当する仕組みはRepositoryです。Repositoryは、create, update, delete, read_one, read_manyなどの、データストアに対する基本的な操作のインターフェイスを規定します。Modelクラスは、Repositoryへの参照を持っていて、 Modelクラスによるデータストアへのアクセスは、全てRepositoryを介して行われます。この際、データストアへのクエリは、SQLではなく Queryというオブジェクトによって渡されます。Queryは、conditionsやlimit, order, fieldsなどの、クエリに必要な情報を保持し、Repositoryを介してデータストアに要求を伝える役割を持っています。

このため、DataMapperはSQLベースのRDBMSだけでなく、CouchDBやGoogle App EngineのDataStoreなど、様々なタイプののデータストアに対応する事ができます。

Adapterの仕組み

Repositoryが様々なタイプのデータストアに対してリクエストを発したり、結果を受け取る際に、Repositoryとデータストアの間を取り持つのがAdapterの役割です。DataMapperでは、全てのAdapterに共通する抽象基底クラスとして、AbstractAdapterが用意されています。また、MySQLやPostgres, Sqlite3などのRDBMS全般のアダプタの共通部分をくくりだしたDataObjectAdapterも用意されています。

Adapterの仕事大きく分けて二つあります。

一つは、Repositoryから受け取ったQueryを解釈し、実際のデータストアが理解できる形式(例えばSQLなど)に変換して実行する事です。

そしてもう一つは、クエリの結果を返す事です。クエリの結果が単体の場合、リソースオブジェクトが直接返されますが、クエリの結果が集合である場合には、Collectionオブジェクトとして返されます。Collectionオブジェクトは、LazyArrayの派生クラスとして実装されていて、要素へのアクセスがあった場合に、データストアへのリクエストを遅延発行させる仕組みを備えています。

Property

DataMapperの大きな特徴の一つが、テーブル定義をmigrationファイルで行わずに、モデルクラスの中で行う事です。以下のように、モデルクラスのクラス定義文で、DSL的にpropertyメソッドを呼んでモデルの属性を定義します。

リスト1 Personモデルの定義例
class Person
  include DataMapper::Resoruce

  property :id, Serial
  property :first_name, String
  property :last_name, String
end

ここで定義された属性は、Adapterがマイグレーションに対応していれば rake db:automigrate もしくは rake db:autoupgrade でデータストアに反映させる事ができます。テーブル構造が変わっても、autoupgradeを実行するだけでインクリメンタルに変更を適用してくれるのがとても便利です。

また、dm_shouldというライブラリを使う事で、以下のように propertyブロック内にvalidationを記述する事ができます。

リスト2 バリデーションの設定例
class Person
  include DataMapper::Resource

  property :id, Serial
  property :first_name, String do
    should be_present
  end
  property :last_name, String do
    should be_present
  end
  property :email, String do
    should match(:email)
  end
end

validationを行うためにはdm-validationsというGemもありますが、RSpecが大好きな人にはこちらがお勧めです。

まとめ

DataMapperはデータストアを抽象化したRepositoryを介してデータを操作するため、ActiveRecordなどと比べると全体的にRDBMSなどの内部構造に縛られない作りになっています。そのため、様々なデータストアを利用する事ができます。

Merbと統合される予定のRails3の開発が進む中、DataMapperやActiveRecordなどのORMを抽象化する存在として ActionORM(fka ActiveORM)という名前も出てきていますが、DataMapperのRepositoryの仕組みをベースに実装するといい感じにまとまるのではないかと思います。RackのORM版とも言えるようなシンプルなAPIを提供してくれると良いですね。

おすすめ記事

記事・ニュース一覧