DataMapper を使う (モデル定義)

http://d.hatena.ne.jp/KrdLab/20090503/1241331627 の補足.
モデル定義についてです.

一部のサンプルは公式サイトから拝借しています.

プロパティの宣言

モデルクラス内部で property メソッドを呼び出す.

class Post 
  include DataMapper::Resource
  property :id,         Serial                         # primary serial key
  property :title,      String,     :nullable => false # null 値不可
  property :published,  Boolean,    :default  => false # デフォルト値 = false
end

DataMapper.auto_migrate! すると以下のようになる.

+-----------+-------------+------+-----+---------+----------------+
| Field     | Type        | Null | Key | Default | Extra          |
+-----------+-------------+------+-----+---------+----------------+
| id        | int(11)     | NO   | PRI | NULL    | auto_increment |
| title     | varchar(50) | NO   |     | NULL    |                |
| published | tinyint(4)  | YES  |     | 0       |                |
+-----------+-------------+------+-----+---------+----------------+

主キー (primary key)

primary key は,ActiveRecord の様に自動的には作成されない.
また,最低でも 1 つの key property を設定しなければならない.
例えば,

class Dummy
  include DataMapper::Resource
  property :name, String
end

と定義し,使ってみると,以下のようなエラーが発生する.

/var/lib/gems/1.8/gems/dm-core-0.9.11/lib/dm-core/resource.rb:622:in `assert_valid_model': Dummy must have a key. (DataMapper::IncompleteResourceError)

DataMapper は Identity Map のインデックスとして,モデルの primary key を使う.


primary key としてよく用いられる auto-incrementing な整数は,以下のように指定できる.

property :id, Serial


id の様な,レコードの一意性を確保するための人為的なキー (人工キー) ではなく,入力データとして与えられる値を主キー (自然キー (natural key)) にする場合は,property 定義にオプションとして :key => true を渡せばよい.

property :name,  String, :key => true

複合キー (composite key)

複数の property に :key 設定すればよい.

class Post
  include DataMapper::Resource
  property :old_id, Integer, :key => true
  property :new_id, Integer, :key => true
end

デフォルト値の指定

プロパティに対して :default を指定すれば,デフォルト値を設定できる.
static な値だけではなく,Proc によるデフォルト値を指定することもできる.

Proc は次の 2 引数をとる.

  • プロパティが設定されるインスタンス自身 (引数 "r")
  • プロパティ自身 (引数 "p")

(※ ブロック内部で "p" を参照すると,stack level too deep (SystemStackError) となる.そりゃそうか.でも何に使うんだろう?)

class Post
  include DataMapper::Resource
  property :id,         Serial
  property :title,      String,   :nullable => false
  property :published,  Boolean,  :default  => false
  property :md5,        String,   :default  => Proc.new {|r, p| Digest::MD5.hexdigest(r.title) if r.title }
end

で,呼び出すと,

Post.create(:title => 'ほげほげ')
+----+--------------+-----------+----------------------------------+
| id | title        | published | md5                              |
+----+--------------+-----------+----------------------------------+
|  1 | ほげほげ     |         0 | 69e285bae22889ad148c8e81dfeaabdd |
+----+--------------+-----------+----------------------------------+

md5 は,Post を new したり create したりするタイミングで設定される.
(new/create で :title を指定しないと NULL になる)

遅延ロード (lazy loading)

プロパティは遅延ロードさせることができる.
遅延ロードするプロパティは,レコードを取得した段階ではロードされず,そのプロパティが最初に呼ばれたときロードされる.
テキストフィールドはデフォルトで遅延ロードになる (←しないようにすることもできる).

class Post
  include DataMapper::Resource
  property :id,     Integer,  :serial => true
  property :title,  String
  property :body,   Text                        # 遅延ロードする (デフォルト)
  property :notes,  Text,     :lazy => false    # 遅延ロードしない
end


複数のプロパティをグループ指定することで,一括して遅延ロードさせることもできる.

class Post
  include DataMapper::Resource
  property :id,         Integer,  :serial => true
  property :title,      String
  property :subtitle,   String,   :lazy => [:show]
  property :body,       Text,     :lazy => [:show]
  property :views,      Integer,  :lazy => [:show]
  property :summary,    Text
end

例えば,データがこんなのだったとする.

+----+---------------------------+----------------------+--------------------------------+-------+--------------+
| id | title                     | subtitle             | body                           | views | summary      |
+----+---------------------------+----------------------+--------------------------------+-------+--------------+
|  1 | KrdLab の不定期日記       | DataMapper を使う    | 使い方とかほげほげ.           |     2 | 概要です     |
+----+---------------------------+----------------------+--------------------------------+-------+--------------+

上の例では,Post.get(1) したとき,id と title だけがロードされる (summary(Text) はデフォルトで遅延ロード).

#<Article id=1
          title="KrdLab \343\201\256\344\270\215\345\256\232\346\234\237\346\227\245\350\250\230"
          subtitle=<not loaded>
          body=<not loaded>
          views=<not loaded>
          summary=<not loaded>>

subtitle/body/views のどれか 1 つにアクセスすると,:show グループに属する 3 つの値は一括してロードされる.
summary は,それ自体を呼ばない限りロードされない.

#<Article id=1
          title="KrdLab \343\201\256\344\270\215\345\256\232\346\234\237\346\227\245\350\250\230"
          subtitle="DataMapper \343\202\222\344\275\277\343\201\206"
          body="\344\275\277\343\201\204\346\226\271\343\201\250\343\201\213\343\201\273\343\201\222\343\201\273\343\201\222\357\274\216"
          views=2
          summary=<not loaded>>

summary にアクセすればロードされる.

#<Article id=1
          title="KrdLab \343\201\256\344\270\215\345\256\232\346\234\237\346\227\245\350\250\230"
          subtitle="DataMapper \343\202\222\344\275\277\343\201\206"
          body="\344\275\277\343\201\204\346\226\271\343\201\250\343\201\213\343\201\273\343\201\222\343\201\273\343\201\222\357\274\216"
          views=2
          summary="\346\246\202\350\246\201\343\201\247\343\201\231">

アクセス制限

プロパティの accessor はデフォルトで public になる.
:accessor オプションを用いることで private や protected にすることができる.

class Post 
  property :title, String, :accessor => :private   # reader/writer ともに private
  property :body,  Text,   :accessor => :protected # reader/writer ともに protected
end

:writer / :reader オプションを用いることで,reader は public,writer は private,といった指定をすることができる.

class Post
  property :title, String, :writer => :private    # reader は public,writer は private
  property :tags,  String, :reader => :protected  # reader は protected,writer は public
end

accessor の override

プロパティのあとに accessor の定義を追加すれば良い.

class Post
  property :name,  String
 
  def name=(new_name)
    raise ArgumentError if new_name != 'KrdLab'
    attribute_set(:name, new_name)   # attribute_set を使うと dirty? で判定できるようになる
  end
end

利用可能な型

DM-Core は,次のデータ型をサポートする.

TrueClass, Boolean, String, Text, Float, Fixnum, Integer, BigDecimal, DateTime, Date, Time, Object, (marshalled)
Discriminator (all you need for Single Table Inheritance, actually)


DM-Types を include した場合は,次のデータ型がサポートされる.

Csv, Enum, EpochTime, FilePath, Flag, IPAddress, URI, Yaml, Json, BCryptHash, Regex

そのほかの型については以下のページを参照.
http://datamapper.org/doku.php?id=dm-more:dm-types