<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
     xmlns:dc="http://purl.org/dc/elements/1.1/"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xml:lang="ja">
  <channel>
    <title>Data::Model - JPerl Advent Calendar 2009</title>
    <link>http://perl-users.jp/articles/advent-calendar/2009/data-model/</link>
    <description>Data::Modelの解説や、ちょっとした tips などを紹介していきます。</description>
    <item>
      <title>あとがき</title>
      <link>http://perl-users.jp/articles/advent-calendar/2009/data-model/25.html</link>
      <description><![CDATA[<div class="section">
<p>ほんとに無謀な一人 advent calendar thone だと思いましたが無事に完走出来ました。</p>

<p>とちゅうは dann さんのありがたくて作り手としても良いところを突いてくれたと思える良記事を出して頂き嬉しかったです。</p>

<p>あと良き競合プロジェクトの nekokak さんの dbix-skinny の track もあったおかげでここまで書けました。</p>

<p>もともとこのチャレンジを始めたきっかけですが Data::Model 自体のドキュメントがあまりにも整備されていなくて折角だから advent calendar という機会に任せてドキュメントを充実させてみようと思って始めました。</p>

<p>完走して終わりではありません。これの成果を元にドキュメントをきっちり書くところまでやりたいと思います。</p>
</div>
<div class="section">
<h3> 今後の Data::Model</h3>

<p>今後の予定ですが、夏に sfujiwara さんからいただいた Pg 対応の取り込みやら、 リレーショナルスキーマのサポートやら細かい fix 項目など TODO はまだまだあります。</p>

<p>Data::Model はリレーショナルしないっていってたじゃん！ってツッコミもありますが、いい実装方法を思いついたのでやってみようと思います。</p>

<p>もし今回の企画で Data::Model に興味をもって使ってみようと思っていただけたら嬉しいです。</p>

<p>不明点やらなんやらがあれば #perl-casual@freenode やら #data-model@irc.perl.org やら #dbix-skinny@irc.perl.org やら適当なところで捕まえてくれればとおもいます。</p>

<p>ではでは、本当に僕の advent calendar 2009 はこれにておわりです。</p>

<p>水着もってバカンスいってきます。</p>
</div>
]]></description>
      <dc:creator>yappo</dc:creator>
      <pubDate>Fri, 25 Dec 2009 06:22:02 GMT</pubDate>
      <category></category>
    </item>
    <item>
      <title>ドライバを作る</title>
      <link>http://perl-users.jp/articles/advent-calendar/2009/data-model/24.html</link>
      <description><![CDATA[<div class="section">
<h3> はじめに</h3>

<p>さぁ、何日か立て続けに Data::Model に用意されている各種 Driver の使い方を紹介してきました。</p>

<p>Driver 編最終回は Driver を作る方法について紹介しましょう。</p>
</div>
<div class="section">
<h3> Driver を作るために必要な知識</h3>

<p>Driver を作るためにはある程度規則に従う必要があります。</p>
<p>ゼロから独自の Driver を作るには Data::Model::Driver の中で空定義してあるメソッドを上書きする必要があります。</p>
<p>そして Data::Model::Driver の各メソッドは Data::Model より delegation されているので、 Data::Model のコードも読んでおく必要があります。</p>

<p>このあたりはドキュメントされていません。</p>
<p>また、内部 API が変わってしまうと互換性が無くなる可能性もあり厄介です。</p>

<p>ということで本日は、需要がありそうで簡単そうな Driver を作る方法を軽く紹介します。</p>
</div>
<div class="section">
<h3> Driver::DBI を拡張する</h3>

<p>主に一昨日紹介した Driver::DBI::MasterSlave の挙動が気にくわないと言った事で作りたい欲求が出るでしょう。</p>

<p>Driver::DBI::MasterSlave は Driver::DBI の dbh のやりくりを制御する部分を独自にハンドリングして master, slave の dbh を切り替えるという要求にしたがって作られています。</p>

<p>rw_handle, r_handle が返す値をうまい具合にやりくりすれば、独自のレプリケーション対応の Driver がかけます。</p>

<p>Driver::DBI::MasterSlave のコードを元にして、どのあたりをいじれば良いのかを紹介しましょう。</p>

<h4> 継承するクラス</h4>

<p>なので、しちめんどい set,get,update,delte,lookup などの処理は再実装しないで Driver::DBI にやってもらえばいいので、これを継承します。</p>

<pre>
package Data::Model::Driver::DBI::MasterSlave;
use strict;
use warnings;
use base 'Data::Model::Driver::DBI';
</pre>

<h4> クラス初期化</h4>

<p>Data::Model::Driver の初期化は new メソッドの引数を全て bless { %args }, $class のようにして保存してから、 init メソッドを単純に呼び出しています。</p>
<p>なので、独自 Driver の初期化処理は init メソッドの中で行います。</p>

<pre>
sub init {
    my $self = shift;
    my $master = $self->{master}
        or Carp::croak "'master' configuration is required";
    my $slave  = $self->{slave} || $master;

    if (my($type) = $master->{dsn} =~ /^dbi:(\w*)/i) {
        $self->{dbd} = Data::Model::Driver::DBI::DBD->new($type);
    }
    $self->{dbi_config} = +{
        master => +{ %{ $master } },
        slave  => +{ %{ $slave } },
    };
}
</pre>

<p>今回は rw_handle, r_handle を自由に差し替えたいという要求を設定しました。</p>
<p>Driver::DBI のでは、このあたりも自由に差し替えするコードを書きやすくしてあります。</p>

<p>状況に応じた DBI のインスタンスを複数作れる用になっており、複数のインスタンスを作るためには DBI の設定を複数設定しておく準備が必要です。</p>

<p>具体的には $self->{dbi_config} に config_name => $config という形で HASH リファレンスを指定します。</p>

<p>Driver::DBI::MasterSlave では master と slave という設定名を使って、二つの設定を保存しています。</p>

<p>$self->{dbd} に Data::Model::Driver::DBI::DBD のインスタンスを入れているところは Driver::DBI で各種 DBD に対応した SQL を吐くために必須ですので注意してください。</p>
</div>
<div class="section">
<h3> dbh を使い分ける</h3>

<p>さぁ DBI インスタンスの設定を複数作ったら、あとはそれぞれ使うだけです。</p>

<p>これはrw_handle と r_handle のメソッドをそれぞれ上書きします。</p>

<pre>
sub rw_handle { shift->_get_dbh('master', @_) };
# トランザクション中は master のみを返す
sub r_handle  { my $self = shift;$self->_get_dbh( ($self->{active_transaction} ? 'master' : 'slave'), @_ ) };
</pre>

<p>見れば分かりますが $self->_get_dbh(config_name) といった形で _get_dbh のプライベートメソッドを読んでいます。</p>

<p>通常は、このように $self->{dbi_config} に格納した設定名を引数にして呼び出せば、その設定を引数にして自動的に DBI インスタンスを作ってくれるので、それを戻すだけでやりたい事が出来ます。</p>

<p>r_handle では $self->{active_transaction} が真だったら master を見るようにしていますが、これは txn_scope 下では常に master を見るべきという設計によるものです。</p>
<p>今の Data::Model の Driver::DBI では、このあたりもハンドリングしてあげる必要があります。</p>

<h4> 応用</h4>

<p>例えば複数台の slave の設定を設定してランダムにその slave を使いたい場合は下記の用な Driver を書きます。</p>

<pre>
package Data::Model::Driver::DBI::ManySlave;
use strict;
use warnings;
use base 'Data::Model::Driver::DBI::MasterSlave';

sub init {
    my $self = shift;
    my $master = $self->{master}
        or Carp::croak "'master' configuration is required";
    my $slave  = $self->{slave} ? ref($self->{slave}) eq 'ARRAY'
        ? $self->{slave} : [ $self->{slave} ] : [ $master ];
    $self->SUPER::init(
        master => $master,
        slave  => $slave,
    );
}
</pre>

<p>slave のオプションを ARRAY ref にする感じです。</p>
<p>殆ど Driver::DBI::MasterSlave の実装を使いまわすので、ここは Data::Model::Driver::DBI::MasterSlave をそのまま継承します。</p>

<p>次は rw_handle と r_handle かと思いますが、 Driver::DBI::MasterSlave の物をそのまま使います。</p>

<p>では、 slave の r_handle を複数から選択するのは選択するの?という疑問ですが、新しく紹介するメソッドを上書きして使います。</p>

<p>dbi_config というメソッドを上書きします。</p>

<p>$self->{dbi_config} に DBI への設定を入れていたと思いますが、この設定を取り出すためのメソッドとして定義されています。</p>

<pre>
sub dbi_config {
    my($self, $name) = @_;
    return $self->{dbi_config}->{master} if $name eq 'master';
    my $slave = $self->{dbi_config}->{slave};
    return $slave->[rand(@{ $slave })];
}
</pre>

<p>このようにして master の時は $self->{dbi_config}->{master} を返して、 slave の時は slave の設定をどれかランダムで返すのです。</p>

<p>DBI のインスタンスを作る為のメソッドの中では dbi_config を使って DBI の設定を取得しているので、ここだけを変更すればうまく行きます。</p>

<h4> 使ってみる</h4>

<p>さて、この作った Driver::DBI::ManySlave を使ってみますか。</p>

<pre>
my $many = Data::Model::Driver::DBI::ManySlave->new(
    master => {
        dsn => 'dbi:mysql:host=master.server:database=test',
    },
    slave => [
        { dsn => 'dbi:mysql:host=slave1.server:database=test' },
        { dsn => 'dbi:mysql:host=slave2.server:database=test' },
        { dsn => 'dbi:mysql:host=slave3.server:database=test' },
        { dsn => 'dbi:mysql:host=slave4.server:database=test' },
    ],
);
</pre>

<p>これだけです。</p>

<h4> とりとめのない話</h4>

<p>これだけの為にわざわざコード書くのはちょっと面倒なので Driver::DBI の設定だけでうまく行くようにしようと思います。</p>

<p>現状でも微妙に出来そうなコード片が入ってるのですが、完璧じゃないのでもすこし書き直してから公開しようとおもいます。</p>

</div>
<div class="section">
<h3> Driver::Cache を拡張する</h3>

<p>さて、次は透過的なキャッシュをする Driver を独自の物に書いてみましょう。</p>

<p>標準では Perl 固有の HASH の中にキャッシュするか、 Memcached なオブジェクトへのキャッシュしか選択出来ません。</p>

<p>しかし Driver::DBI と比べてもさらにシンプルなんです。</p>

<p>基本的な Driver としての実装は Data::Model::Driver::Cache の中で実装されており、これを継承して Driver::Cache 用のインターフェイスを満たせば OK なんです。</p>

<p>これも既存の Driver::Cache::HASH のコードを元に説明しましょう。</p>

<h4> データ追加</h4>

<p>データの追加するメソッドを定義します。</p>

<pre>
sub add_to_cache {
    my($self, $key, $data) = @_;

    my $ret = $CACHE{$key} = $data;
    return if !defined $ret;
    return $ret;
}
</pre>

<p>add_to_cache の第一引数に key を、第二引数に value が渡されます。</p>
<p>成功したら value をそのまま返してください。</p>
<p>失敗時は undef を返します。</p>

<h4> データ取得</h4>

<p>データを取得する処理で使われます</p>

<pre>
sub get_from_cache {
    my($self, $key) = @_;

    my $ret = $CACHE{$key};
    return if !defined $ret;
    return $ret;
}
</pre>

<p>get_to_cache の第一引数に key が渡されます。</p>
<p>成功したら key に対応する value をそのまま返してください。</p>
<p>失敗時は undef を返します。</p>

<h4> データ削除</h4>

<p>データを削除する処理で使われます</p>

<pre>
sub remove_from_cache {
    my($self, $key) = @_;
    
    my $ret = delete $CACHE{$key};
    return if !defined $ret;
    return $ret;
}
</pre>

<p>remove_from_cache の第一引数に key が渡されます。</p>
<p>失敗したら undef を返してください。</p>
<p>成功したら undef 以外を返してください。</p>

<p>今現在 0 や '' などを返しても失敗したと誤認識するバグが発見されました。</p>

<h4> その他</h4>

<p>update 処理は、該当する key の削除のみを行うという挙動になっています。</p>

<p>トランザクションとの組み合わせは、現在完全な透過処理が行われません。</p>

<p>lookup_multi 系のクエリは get_multi_from_cache を上書きします。</p>
<p>以下に Memcached で利用してるコードを張り付けます。</p>

<pre>
sub get_multi_from_cache {
    my($self, $keys) = @_;

    my $ret = $self->{memcached}->get_multi($keys);
    return if !defined $ret;
    return $ret;
}
</pre>
</div>
<div class="section">
<h3> まったく新規に Driver を作る</h3>

<p>もうちょっと詳細に書く予定でしたが、基本的に本日紹介した方法を見れば大体の Driver 作成の要求を満たせるかなと思ったので今回は省略させてください。</p>

<p>もし、そのような需要がある場合は Yappo を捕まえて相談してみてください。</p>
</div>
<div class="section">
<h3> 他の DBD 対応</h3>

<p>Driver とは直接関係ないですが mysql や SQLite 以外の DBD 対応へのポインタを示します。</p>

<p>Data::Model::Driver::DBI::DBD 以下の名前空間の実装を見てください。</p>

<p>基本的に SQL generator からの delegation されるコードですので delegation 元の Data::Model::Schema::SQL などを読んでみてください。</p>

<p>DBD::Pg に関しては sfujiwara さんが実装してくださったので僕の merge まちです＞＜</p>
</div>
<div class="section">
<h3> まとめ</h3>

<p>本日は Driver hack についてあれこれ書きました。</p>

<p>さぁ、明日はいよいよ最終回です。</p>
</div>
]]></description>
      <dc:creator>yappo</dc:creator>
      <pubDate>Thu, 24 Dec 2009 11:06:01 GMT</pubDate>
      <category></category>
    </item>
    <item>
      <title>データをどのようにキャッシュするか</title>
      <link>http://perl-users.jp/articles/advent-calendar/2009/data-model/23.html</link>
      <description><![CDATA[<div class="section">
<p>こんにちわ！ Yappo です！</p>
<p>2十3日目は Data::Model でどのようにデータをキャッシュし、</p>
<p>キャッシュしたデータをどう扱うかについてです。</p>

<p>アプリケーションの負荷軽減策の一つとしてデータを memcached にキャッシュしてしまい、</p>
<p>DB アクセスをすくなくすることで、レスポンスを早くする手法はみなさん良く使われていると思います。</p>


<p>Data::Model では Driver の機能として、データを透過キャッシュするなどの便利機能が存在しています。</p>
<p>そこで、いろいろ工夫擦る必要は特にありません。 Driver::Cache を使うと透過的キャッシュが利用できます。</p>
<p>このあたりの仕組は Data::ObjectDriver から拝借してます。</p>

<p>ただし Data::Model::Driver::Cache の戦略は全てのキャッシュを透過的に行ってしまうので、アプリケーションによっては無駄にキャッシュしすぎてしまうとかありえます。</p>
<p>ユーザの必要なケースに応じてキャッシュしてもらった方が効率が良いケースももちろんあるでしょうし Driver::Cache を使って楽をしたいときもあるでしょう。</p>
<p>幸い Data::Model では、特定のテーブルの Driver だけ変更する事ができるので一番重くて効果的なテーブルだけ memcached で透過的にキャッシュするという戦略も取れるのです。</p>

<p>では、早速データのキャッシュ方法についてみていきましょう。</p>

<pre>
# base driver を設定
my $driver = Data::Model::Driver::Memory->new;
base_driver $driver;

# user テーブルだけ透過キャッシュを指定
my $cache  = Data::Model::Driver::Cache::HASH->new(
    fallback => $driver,
);
install_model user => schema {
    driver $cache;
};
</pre>

<p>これだけです。</p>
<p>これで user というテーブルだけ透過的に on memory な HASH driver にキャッシュされるようになります。</p>
<p>あとはそのデータが DB から取得したものなのか、 DB から取得したものなのかを気にする必要は全くありません。</p>

<p>fallback というオプションは、 get したときに cache がなかったときに利用される Driver です。</p>
<p>きちんと Data::Model の Driver の規格にあったものなら何でも使えます。</p>
<p>Data::Model::Driver::Cache::HASH の fallback に Data::Model::Driver::Cache::HASH を指定するアホな事もできるし、 Data::Model::Driver::Memcached も使えます。</p>
<p>まぁ KVS ストレージを透過的に memcached でキャッシュするなんて無意味すぎるので誰もやらないけど。</p>

<p>もちろん Driver::Cache を base_driver として指定してもいいです。それは自己責任で。</p>

<p>上記の例は単純な Perl の HASH にキャッシュするだけの Driver でしたが memcached にキャッシュする時は Driver::Cache::Memcached を使います。</p>

<pre>
   my $driver = Data::Model::Driver::Memory->new;
   my $cache_driver = Data::Model::Driver::Cache::Memcached->new(
        fallback  => $driver,
        memcached => Cache::Memcached::Fast->new({ servers => [ { address => "localhost:11211" }, ], }),
    );
</pre>

<p>memcached オプションに任意の Cache::Memcached オブジェクトを入れるだけです。</p>

<p>簡単ですね。</p>

<p>明日は、自作 Driver について掘り下げていこうかと思います。</p>

<p>have a nice data-model days!:)</p>
</div>
]]></description>
      <dc:creator>yappo</dc:creator>
      <pubDate>Thu, 24 Dec 2009 09:48:01 GMT</pubDate>
      <category></category>
    </item>
    <item>
      <title>レプリケーションとQ4Mを使う</title>
      <link>http://perl-users.jp/articles/advent-calendar/2009/data-model/22.html</link>
      <description><![CDATA[<div class="section">
<h3> はじめに</h3>

<p>さぁ Driver 集中講義二回目です。</p>

<p>ホントは別々に書こうと思ったのですが MySQL 関連という事でまとめて紹介します。</p>
</div>
<div class="section">
<h3> レプリケーションを使う</h3>

<p>MySQL にはレプリケーション機能があるのはとても知られている事です。</p>

<p>レプリケーションの無い MySQL は、ブラウン管の無いブラウン管テレビくらいの物でしょう。</p>

<p>そして ORM でも切っても切れない物では無いでしょうか。</p>

<p>Data::ObjectDriver でも livedoor のどっかのサービスで自作 Driver 書いて使ってるとか</p>
<p>DBIC の世界でも DBIx::Class::Storage::DBI::Replicated なんて物があるようです。</p>

<p>もちろん Data::Model でも対応しています。</p>

<p>Driver::DBI::MasterSlave です。 Driver::DBI を使った事がある人ならとても簡単な物となっています。</p>

<pre>
  use Data::Model::Driver::DBI::MasterSlave;
  
  my $dbi_connect_options = {};
  my $driver = Data::Model::Driver::DBI::MasterSlave->new(
      # master の接続設定
      master => {
          dsn => 'dbi:mysql:host=master.server:database=test',
          username => 'master',
          password => 'master',
          connect_options => $dbi_connect_options,
      },
      # slave の接続設定
      slave  => {
          dsn => 'dbi:mysql:host=slave.server:database=test',
          username => 'slave',
          password => 'slave',
          connect_options => $dbi_connect_options,
      },
  );

  # base driver の設定をする
  base_driver $driver;
</pre>

<p>上記の用に master => $conf, slave => $conf という形で設定するだけです。 $conf の中身は先日紹介した Driver::DBI の設定と全く同じ物がつかえます。</p>

<h4> 仕組み</h4>

<p>Driver::DBI も 内部的には DBI のインスタンスを使って DBI 接続を行っていて DBI::MasterSlave では DBI のインスタンスを2個作って、それぞれ master, slave 用として使います。</p>

<p>Driver::DBI は SELECT クエリでは r_handle を使い それ以外のクエリでは rw_handle を使っており、それぞれの handle にたいして master, slave の DBI のインスタンスを割り当てるので、うまい具合 master, slave でクエリを振り分ける事ができるのです。</p>

<h4> 注意</h4>

<p>txn_scope を使ったトランザクション中は、全てのクエリが rw_master に向かいますので注意してください。</p>

<h4> 複数の slave を扱うにはどうするの?</h4>

<p>DBIC の世界だと複数台の slave をうまいこと扱ってくれる仕組みがあるのですが Data::Model では、そのような仕組みはありません。</p>

<p>バグ?手抜き? いえいえ、元からこういう設計です。</p>

<p>そういった複数台の slave を持つ場合には lvs などを使って、より低いレイヤーにて分散環境を整えてください。</p>

<p>このような低いレベルでやるべき処理を Perl のレイヤでやるよりかは、それなりに実績がある低レイヤ処理するべきだと思っています。</p>

<p>しかも slave の分散クエリなんて lvs で十分事足りるし Perl のコード書くよりも高度な事ができるので良いです。</p>

<p>まぁ、インテグレーションというのは時と場合によって思うとおりの道具が使えないでしょうから、細かい要求などは Data::Model::Driver の自分向けに書いてくださいという事になります。この辺の詳しい話は後日書きます。</p>
</div>
<div class="section">
<h3> Q4M を使う</h3>

<p>Data::Model の特色の一つとして Q4M 対応が行われているということです。</p>

<p>このあたりのエッセンスは3日目( <a href="http://perl-users.jp/articles/advent-calendar/2009/data-model/03.html">http://perl-users.jp/articles/advent-calendar/2009/data-model/03.html</a> )に紹介してますが、3日目は超応用編だったので今日は Driver::Queue::Q4M の使い方を紹介します。</p>


<h4> Q4M とは</h4>

<p>Q4M とは、サイボウズラボの奥一穂(以下略</p>

<h4> スキーマ定義</h4>

<p>スキーマ定義は、通常の定義と殆どおんなじ感じです。</p>

<pre>
    package TestQueue;
    use base 'Data::Model';
    use Data::Model::Schema;
    # Queue::Q4M の mixin が必須
    use Data::Model::Mixin modules => ['Queue::Q4M'];
    use Data::Model::Driver::Queue::Q4M;

    my $driver = Data::Model::Driver::Queue::Q4M->new(
        dsn => 'dbi:mysql:database=test'
    );
    base_driver $driver;

    install_model queue_test => schema {
        columns qw/ id job_name /;

        # as_sqls の為にも TYPE=Queue を入れとく
        schema_options create_sql_attributes => {
            mysql => 'TYPE=Queue',
        };
    };
</pre>

<p>Q4M 専用のメソッドを追加するために mixin を指定するのと CREATE TABLE 文の為に schema_options create_sql_attributes を指定します。</p>

<p>as_sqls で出来上がる SQL は下記のようになります。</p>

<pre>
CREATE TABLE queue_test (
    id              CHAR(255)      ,
    job_name        CHAR(255)      
) TYPE=Queue;
</pre>

<p>Q4M では index のサポートが行われていないので、 primary key などの事は忘れましょう。</p>
<p>もちろんユニーク制約なんてのも使えませんからね。</p>

<h4> Queue を作る</h4>

<p>Q4M の Queue を作るのはとても簡単です！</p>
<p>なんてったって普通の MySQL のテーブルの用に扱えるのが Q4M の良いところの一つなんで、普通に set メソッドして INSERT すれば良いんです.</p>

<pre>
    # queue を一つ作る
    $queue->set(
        queue_test => {
            id       => 1,
            job_name => 'get http://example.com/',
        },
    );
</pre>

<h4> Queue を読む</h4>

<p>Q4M を使う場合は queue_wait を使って dequeue してからデータを読むことが一般的ですが抜く、一応 Data::Model ごしでも Queue の内容を直接読むことが出来ます。</p>
<p>ただし primary key とかは無いので where とか使って見る必要があるでしょう。</p>

<p>index を使ってないですが、そもそも Q4M に Queue が大量に保存されてることはありえないので問題にならないでしょう。大量にあるんだったらお前のアプリの書き方がとち狂ってる。</p>

<pre>
    # queue を読む
    my($q) = $queue->get('queue_test');
    warn $q->job_name;
</pre>

<h4> Queue の削除</h4>

<p>普通は Q4M が勝手に削除するんですが、どうしても手動で消したい人の為に書いておきます。</p>
<p>基本的にはスキーマオブジェクトの delete メソッドを叩くだけです。</p>

<pre>
    # こういう delete はできない
    $q->delete;

    # 直接 query を吐いて delete する
    $queue->delete('queue_test', {
        where => [
            id => 1,
        ],
    });

    # DELETE FROM queue_test; はこれ
    $queue->delete('queue_test', {});
</pre>

<p>$q->delete のように Row オブジェクトの delete メソッドは primary key が無いテーブルには使えないため上記のコードのようなコメントになっています。</p>

<h4> update</h4>

<p>(略</p>

<h4> dequeue する</h4>

<p>Q4M では queue_wait を使って dequeue をします、細かいことはドキュメント読んでください。</p>

<p>まぁこんな SQL ですね。</p>

<pre>
mysql> SELECT queue_wait('high_priority_table', 'low_priority_table', 10);
</pre>

<p>これに相当する Data::Model の使い方としては queue_running メソッドを使います。</p>

<pre>
  my $queue = TestQueue->new;
  my $retval = $queue->queue_running(
      high_priority_table => sub {
          my $row = shift;
          # 何かの処理
      },
      low_priority_table  => sub {
          my $row = shift;
          # 何かの処理
      },
      timeout => 10,
  );
</pre>
<p>queue_running の引数に table_name => CODE リファレンス という構造の HASH を渡してあげます。</p>
<p>timeout だけは例外的に queue_wait に渡すタイムアウトを指定します。</p>

<p>table_name の queue table に enqueue されると、オーナーモードになった行の Row オブジェクトを引数としてコードを実行します。</p>

<p>簡単ですね。</p>

<h4> queue_abort</h4>

<p>Q4M では queue が何らかの処理で以上になった時に queue の先頭に enqueue してくれる queue_abort という物があります。</p>
<p>もちろん Data::Model でも使えます。</p>

<pre>
    my $ret = $queue->queue_running(
        queue_test => sub {
            my $row = shift;
            warn $row->id;
            warn $row->job_name;
            $queue->queue_abort;
        },
    );
    warn $ret;
</pre>

<p>このようにスキーマオブジェクトから生えている queue_abort メソッドを呼ぶだけです。</p>
<p>$ret の内容は、 queue_running が失敗してるだけ undef になります。</p>

<p>ちなみに CODE リファレンス中で die してしまった時も内部的には queue_abort されます。</p>

<pre>
    eval {
        my $ret = $queue->queue_running(
            queue_test => sub {
                die "abort"; # ここで queue_abort が呼ばれる
            },
        );
    };
    $@ and warn $@; # 'abort' と表示
    warn $ret;
</pre>

<p>ここの $ret も undef です。</p>

<h4> queue_end</h4>

<p>Q4M で queue の正常終了を表す queue_end という物が用意されています。</p>

<p>Data::Model では CODE リファレンスの中だけが Q4M のオーナーモードでいるという設計になっているので、 CODE リファレンスの中で queue_abort が呼ばれないまま return した時に自動的に queue_end が呼ばれます。</p>

<pre>
    my $ret = $queue->queue_running(
        queue_test => sub {
            return; # ここで queue_end が呼ばれる
        },
    );
    warn $ret; # 処理した queue テーブル名を表示
</pre>

<p>queue の処理が成功した場合に queue_running は queue_wait で処理した queue table 名を戻り値として返します。</p>

<h4> queue_running と queue_abort はどこから来たの?</h4>

<p>冒頭に Queue::Q4M mixin を使う設定をすると書いてありますが、この Mixin がスキーマオブジェクトに queue_running と queue_abort メソッドを生やしているんです。</p>

<p>単純に Driver に処理を delegation してるだけです。</p>
</div>
<div class="section">
<h3> まとめ</h3>

<p>今日は、レプリケーションと Q4M を Data::Model で使う為にどうするかといった事を紹介しました。</p>

<p>サックリ紹介するつもりが長文になって DNBK です。</p>

<p>明日はキャッシュ戦略について紹介します。</p>
</div>
]]></description>
      <dc:creator>yappo</dc:creator>
      <pubDate>Thu, 24 Dec 2009 11:06:01 GMT</pubDate>
      <category></category>
    </item>
    <item>
      <title>ドライバを知る</title>
      <link>http://perl-users.jp/articles/advent-calendar/2009/data-model/21.html</link>
      <description><![CDATA[<div class="section">
<h3> はじめに</h3>

<p>これまでは Data::Model カラムの定義周りを重点的にボリュームを上げて書いていました。</p>
<p>これからは最終回に向けて、より確実に Data::Model を使えるような内容でお送りしたいと思います。</p>
</div>
<div class="section">
<h3> Driver</h3>

<p>Data::Model を ORM 的に使うには use base 'Data::Model' して use Data::Model:Schema して DSL を使ってスキーマ定義をすると説明してきました。</p>

<p>ただ、これだけではデータベースと接続する事ができません。なぜならスキーマ定義にはデータベースと接続する機能がないからです。</p>

<p>データベースなどのデータソースと接続するためにはドライバーを利用しなければなりません。</p>

<p>このあたりは Data::ObjectDriver と同じです。もしかしたら DBIx::MoCo も当てはまるかもしれません。</p>

<p>簡単な Driver の定義は下記のような形です。</p>

<pre>
    use Data::Model::Driver::DBI;
    my $driver = Data::Model::Driver::DBI->new(
        dsn => 'dbi:SQLite:dbname=mybookmark.db'
    );
</pre>

<p>new のオプションは Driver によって異なります。</p>
<p>DBI Driver の全オプションを含めると以下のようなコードになります。</p>

<pre>
  my $driver = Data::Model::Driver::DBI->new(
      dsn             => 'dbi:mysql:host=localhost:database=test',
      username        => 'user',
      password        => 'password',
      connect_options => $dbi_connect_options,
      reuse_dbh       => 1,
  );
</pre>

<p>dsn, username, password などは DBI のあれと同じですね。</p>
<p>connect_options は  DBI->connect( $dsn, $user, $pass, $connect_options ) の $connect_options に渡されるものと同等です。</p>
<p>default で RaiseError => 1, PrintError => 0, AutoCommit => 1 がそれぞれ設定されます。</p>

<p>reuse_dbh は、同じ dsn の接続は1つの接続を使いまわすようにしてくれます。</p>
<p>例えば、 DB::User, DB::Bookmark, DB::Diary, DB::Fotolife と複数のスキーマ定義をしておいて、それぞれ別々の Data::Model::Driver::DBI インスタンスを使っているときに、 dsn は同じ物を利用していた場合に reuse_dbh を真にしておくと DB 接続は一つだけにしてくれるようになります。</p>
</div>
<div class="section">
<h3> 様々な Driver</h3>

<p>Data::Model では DBI 以外にも様々なデータソースが扱えます。代表的な物としては Driver::Memcached なのですが、これは17日に dann さんが素敵な紹介を寄稿していただいたのでそちらを参照ください。</p>
<p><a href="http://perl-users.jp/articles/advent-calendar/2009/data-model/17.html">http://perl-users.jp/articles/advent-calendar/2009/data-model/17.html</a></p>
<p>kumofs にデータを出し入れするために僕は使っています。</p>
<p>この Driver の圧縮オプションをつけまくる事で素の Cache::Memcached などのライブラリを使うよりは利点があると思っています。</p>

<h4> Driver::Memory</h4>

<p>依存ライブラリ無しでかつ on memory でデータの管理をしてくれるドライバです。</p>

<p>sort や where や index, unique を始めとした Driver::DBI とほぼ同等のクエリが利用出来ます。</p>

<p>一応 Driver::DBI と同じテストコードを通しているので品質もそこそこです。</p>

<h4> Driver::Logic</h4>

<p>モデルロジック処理をデータソースとして扱うためのラッパーを書くたに作りましたがちょっと不評ちゃんです。</p>

<p>もうちょっと作戦立ててから作り替えて使いやすくしようと思ってますが、あまり使って欲しくないので undocumented なのです。</p>

<h4> Driver::HASH</h4>

<p>スキーマレスになデータモデルに好きなときに好きなだけ index はれたりするような物を作ろうとしてますが現在コードがありません。</p>

<h4> Driver::DBI::MasterSlave</h4>
<h4> Driver::Queue::Q4M</h4>
<h4> Driver::Cache</h4>

<p>上記3種類はそれぞれ個別で紹介しようと思います。</p>
</div>
<div class="section">
<h3> スキーマ定義の設定</h3>

<p>最初の方でスキーマ定義を下だけではデータソースを使うことができませんとありました。</p>
<p>実際にスキーマ定義と Driver を紐づける必要がありますので以下で紹介します。</p>

<p>ちなみにここでのサンプルは全て Data::Model::Driver::Memory を使ってます余計なコードないので。</p>

<h4> スキーマ定義の中で全テーブルに設定する</h4>

<p>基本的に DB サーバの database の単位や、役割ごとにスキーマ定義ファイルを書くと思いますが、そういった場合はもちろんスキーマクラスの中で定義されている DSN やら DB サーバは同一になるはずなので一括で Driver の設定を行います。</p>

<pre>
package MyDatabase;
use strict;
use warnings;
use base 'Data::Model';
use Data::Model::Schema;
use Data::Model::Driver::Memory;
my $driver = Data::Model::Driver::Memory->new;

# MyBookmark の中で定義したテーブル全てで $driver を使う
base_driver $driver;
</pre>

<p>base_driver 関数でスキーマ定義全体で使う Driver を設定します。</p>

<h4> テーブル毎に Driver を変える</h4>

<p>基本的にはありえないのですが、テーブル毎に driver を変える事が出来ます。</p>

<pre>
package MyDatabase;
use strict;
use warnings;
use base 'Data::Model';
use Data::Model::Schema;
use Data::Model::Driver::Memory;
my $driver1 = Data::Model::Driver::Memory->new;

# MyBookmark の中で定義したテーブル全てで $driver1 を使う
base_driver $driver1;
install_model yappo => schema {
};

my $driver2 = Data::Model::Driver::Memory->new;
install_model nekokak => schema {
    # このテーブルだけ $driver2 をつかう
    driver $driver2;
};

# kan さんは $driver1
install_model kan => schema {
};
</pre>

<p>driver は install_model の定義の中だけで使えます。</p>
<p>base_driver で設定した driver を部分的に上書きするので、 yappo, kan は $driver1 を使って nekokak は $driver2 を使います。</p>


<h4> 使うときに設定する</h4>

<p>スキーマ定義の中だけではなくて、そのスキーマ定義を実際に利用する時に定義することが出来ます。</p>
<p>これは　Web フレームワークの中に Data::Model を組み込んだ際に実行環境によって Driver の設定を変えたい時に利用すると便利になります。</p>

<pre>
    my $model = MyDatabase->new;

    # MyDatabase の base_driver として $driver1 を設定する
    my $driver1 = Data::Model::Driver::Memory->new;
    $model->set_base_driver( $driver1 );

    # MyDatabase の nekokak テーブルの driver として $driver2 を設定する
    my $driver2 = Data::Model::Driver::Memory->new;
    $model->set_driver( nekokak => $driver2 );
</pre>

<p>スキーマ定義の時に使った driver と base_driver に set_ の接頭辞をつけたメソッドをスキーマオブジェクトにはやして使います。</p>
</div>
<div class="section">
<h3> その他関連メソッド</h3>

<p>今これを書いてる時に気がついたのですがスキーマクラスに生えている set_driver や set_base_driver が undocument でした。</p>

<p>ついでに関連する未文書なメソッドを紹介します。</p>

<h4> driver 定義を取得する</h4>

<p>set_driver や set_base_driver のついとなる get_driver などのメソッドがあります。</p>

<pre>
    my $model = MyDatabase->new;

    # MyDatabase の base_driver を取り出す
    $model->get_base_driver;

    # MyDatabase の nekokak テーブルの driver をとりだす
    $model->get_driver( 'nekokak' );
</pre>

<h4> 定義されたテーブルの一覧を取り出す</h4>

<p>スキーマ定義にて定義した install_model もテーブルの一覧を返すメソッドがあります。</p>

<pre>
    my $model = MyDatabase->new;
    # 定義されたテーブルのリストを返す
    # yappo, nekokak, kan が返る
    say join ',', $model->schema_names;
</pre>

<p>普通の人はあまり使わないかもしれませんが Data::Model->as_sqls にて以下のように使っています。</p>

<pre>
sub as_sqls {
    my $self   = shift;
    my $target = shift;
    my @sql = ();
    for my $model ($self->schema_names) {
        next if $target && $model ne $target;
        push @sql, $self->get_schema($model)->sql->as_sql;
    }
    @sql;
}
</pre>
</div>
<div class="section">
<h3> まとめ</h3>

<p>本日は Data::Model の重要な要素の Driver に関して紹介しました。</p>
<p>どのようにしてデータソースとつなげるかが理解できたかと思います。</p>

<p>これからラストまで Driver 関連の解説を行う予定です。</p>
</div>
]]></description>
      <dc:creator>yappo</dc:creator>
      <pubDate>Thu, 24 Dec 2009 07:47:01 GMT</pubDate>
      <category></category>
    </item>
    <item>
      <title>トランザクションについて</title>
      <link>http://perl-users.jp/articles/advent-calendar/2009/data-model/20.html</link>
      <description><![CDATA[<div class="section">
<p>こんにちわ！ Yappo です！</p>
<p>2十日目は Data::Model でのトランザクションの使い方です。</p>

<p>トランザクションについての説明は省きます。</p>
<p><a href="http://e-words.jp/w/E38388E383A9E383B3E382B6E382AFE382B7E383A7E383B3.html">http://e-words.jp/w/E38388E383A9E383B3E382B6E382AFE382B7E383A7E383B3.html</a></p>
<p>このあたりを読めばいいんじゃないでしょうか。</p>

<p>早速試しましょう。</p>
<p>ちなみに実験するときはトランザクションに対応しているRDBMSを使ってください。</p>
<p>MySQL の場合だと MyISAM でためしても MyISAM がトランザクションに対応していないのでいみがありません。</p>
<p>InnoDBで試しましょう。</p>

<p>なお、 install_model　にて CREATE TABLE 文を作るときのテーブルオプションを与える事で TYPE=InnoDB のような CREATE TABLE SQL が作成出来ます。</p>

<pre>
    install_model txn_test => schema {
        key 'id';
        columns qw( id name );
        schema_options create_sql_attributes => {
            mysql => 'TYPE=InnoDB',
        };
    };
</pre>

<p>のようにして  schema_options を設定します。</p>
<p>create_sql_attributes は CRATE TABLE NAME (); の attribute のとこに任意の SQL を追加できるというオプションで、任意の SQL を HASH リファレンスの中に書きます。</p>
<p>HASH リファレンスの key は DBD ドライバ名で mysql や SQLite などが指定でき、 value に実際追加したい SQL を書きます。</p>

<p>上記の定義を as_sqls すると下記のようになります。</p>

<pre>
CREATE TABLE txn_test (
    id              CHAR(255)      ,
    name            CHAR(255)      ,
    PRIMARY KEY (id)
) TYPE=InnoDB;
</pre>
</div>
<div class="section">
<h3> 使ってみる</h3>

<pre>
# トランザクションの開始
my $txn = $db->txn_scope;

# INSERT
$txn->set(
    txn_test => 1 => { name => 'Yappo' }
);

# トランザクションを終了してデータを commit する！
$txn->commit;
</pre>

<p>このように書くと、 txn_scope メソッドを呼び出したときに帰ってくるオブジェクトを保持しているあいだ</p>
<p>トランザクションが有効となります。</p>
<p>なので</p>

<pre>
# トランザクションの開始
my $txn = $db->txn_scope;

# LOOKUP
my $row = $txn->lookup( txn_test => 1 );

# data の update
$txn->update($row);

die 'oooops';

# トランザクションを終了してデータを commit する！
$txn->commit;
</pre>

<p>set/update/delete メソッドの後の処理が die などで異常終了した場合、</p>
<p>set/update/delete メソッドでの更新が rollback されます。</p>

<p>もっと詳しくいうと、 $txn のスコープが外れた時に $txn->commit されていなければ強制的に rollback されます。</p>
<p>これは DBIx::Class::Storage::TxnScopeGuard とほとんど同じ挙動です。</p>
</div>
<div class="section">
<h3> 通常との違い</h3>

<p>上記の例では、 $db->set を使わずに $txn->set などを使って INSERT していました。</p>
<p>これは、トランザクションスコープ内では直接レコードの更新を許さないという設計によるものです。</p>

<p>START TRANSACTION してる間に他のメソッドを呼びだして、そのメソッドの中で意図しない更新クエリが走った時に抑制してくれたりします。</p>

<p>実際にトランザクションスコープ中に通常の方法で更新系を呼び出すと、下記のようにエラーになります。</p>

<pre>
my $txn = $db->txn_scope;
eval {
    $db->set(
        txn_test => 1 => { name => 'Yappo' }
    );
};
warn $@ if $@;# say "The 'set' method can not be performed during a transaction."

eval {
    $row->delete;
};
warn $@ if $@;# say "The 'delete' method can not be performed during a transaction."
</pre>

<p>これは、 lookup などして取得した Row オブジェクトなどに生えている update, delete メソッドに対しても有効となっています。</p>

<p>なので、更新系は txn_scope が返すインスタンス経由で実行する必要があります。</p>
<p>Row オブジェクトの update, delete メソッドは $txn->update($row) などとして txn_scope が返すインスタンスのメソッド経由で更新します。</p>

<pre>
# INSERT
$txn->set(
    txn_test => 1 => { name => 'Yappo' }
);

# LOOKUP
my $row = $txn->lookup( txn_test => 1 );
warn $row->name;

# UPDATE
$row->name('nekokak');
$txn->update($row);

# GET
my $itr = $txn->get('txn_test');
while (<$itr>) {
    warn $_->name;
}

# delete
$txn->delete($row);

$txn->commit;
</pre>

</div>
<div class="section">
<h3> まとめ</h3>

<p>トランザクションをうまく使うとデータの一貫性を保証した処理を書くことができますので</p>
<p>びしばしつかってみてください。</p>

<p>have a nice data-model days!:)</p>

</div>
]]></description>
      <dc:creator>yappo</dc:creator>
      <pubDate>Tue, 22 Dec 2009 08:37:02 GMT</pubDate>
      <category></category>
    </item>
    <item>
      <title>カラムの定義を使いまわす3 Alias との連携</title>
      <link>http://perl-users.jp/articles/advent-calendar/2009/data-model/19.html</link>
      <description><![CDATA[<div class="section">
<h3> はじめに</h3>

<p>本日で Column Sugar 集中講座最終日です。気合入れて行きましょう。</p>

<p>前回では Column.pm に Column Sugar の定義を移動してきました、記憶力の良い方は覚えているでしょうが Alias Column の時に例題で出していた global.epoch も移行していました。</p>
<p>global.epoch は create_dt という alias を張って DateTime で inflate してました。以下のような感じです。</p>

<pre>
install_model bookmark => schema {
# 略
    column 'global.epoch' => 'create_at' => {
        default => sub { time() },
    };
    alias_column create_at => create_dt => {
        inflate => 'DateTime',
    };
</pre>

<p>これとおんなじ事を diary でもやりたくなると</p>

<pre>
install_model create => schema {
# 略
    column 'global.epoch' => 'create_at' => {
        default => sub { time() },
    };
    alias_column create_at => create_dt => {
        inflate => 'DateTime',
    };
</pre>

<p>のようにおんなじコピペだらけになってしまいます。。。。</p>

<p>そこで登場するのが Column Sugar の Alias 設定です。</p>
</div>
<div class="section">
<h3> 定義する</h3>

<p>まずは Column Sugar の定義を変更します。</p>
<p>前回までは以下の形だったのを</p>

<pre>
column_sugar 'global.epoch'
    => int => {
        required => 1,
        unsigned => 1,
    };
</pre>

<p>次のように変更します。</p>

<pre>
column_sugar 'global.epoch'
    => int => {
        required => 1,
        unsigned => 1,
        alias => {
            epoch_dt => {
                inflate => 'DateTime',
            },
            epoch_dt_utc => {
                inflate => 'DateTimeUTC',
            },
            epoch_dt_jst => {
                inflate => 'DateTimeJST',
            },
        },

    };
</pre>

<p>alias という値を追加します。ここで与える HASH リファレンスは aliased_name => \%append_params という形式になっています。</p>

<p>新しいエイリアスカラム名を key にして、それに対する追加カラム定義を value とした HASH リファレンスです。</p>

<p>HASH リファレンスなので、複数の定義も同時に行えるので epoch_dt, epoch_dt_jst, epoch_dt_utc と作ってみました。</p>
<p>DateTimeUTC, DateTimeJST それぞれの Inflate 定義の中身は割愛します。</p>
</div>
<div class="section">
<h3> 定義を利用する</h3>

<p>こうして作ったエイリアス定義済みの Column Sugar を使ってみましょう。</p>
<p>もちろん</p>

<pre>
    column 'global.epoch' => 'create_at' => {
        default => sub { time() },
    };
</pre>

<p>すれば, table.create_at という形で使えます。使えますが、 alias されたカラムを使う時は $table->create_dt とかなって欲しいのに $table->epoch_dt とかになってしまいます。</p>
<p>これはちょっと混乱して大変ですよね。</p>

<p>なので、この Column Sugar で定義されたエイリアスカラムのカラム名を変更する仕組みも用意されています。</p>

<p>下記のようにして column 定義することによりリネームできます。</p>

<pre>
    column 'global.epoch' => 'create_at' => {
        default      => sub { time() },
        alias_rename => {
            epoch_dt     => 'create_dt',
            epoch_dt_utc => 'create_dt_utc',
            epoch_dt_jst => 'create_dt_jst',
        },
    };
</pre>

<p>column 定義の追加定義欄に alias_rename という key で HASH リファレンスを渡します。</p>
<p>HASH リファレンスの内容は、元のエイリアス名 => リネーム後のエイリアス名 という形です。これも複数個指定可能です。</p>
</div>
<div class="section">
<h3> 使ってみる</h3>

<p>ここまで準備できれば、あとは何も考える必要がありません。</p>
<p>alias されたカラムメソッドを使うだけです。</p>
<p>複数にエイリアスを張っていた時に、どれかのエイリアスの値を変更してもきちんとすべてのエイリアスの値は変更されています。</p>

<p>試しに以下のように使ってみますか。</p>

<pre>
    my $yappo = $bookmark->set( user => { nickname => 'Yappo' } );
    my $diary = $diary->set(
        diary => {
            user_id => $yappo->id,
        }
    );

    printf "create_at    : %s\n", $diary->create_at;
    printf "create_dt    : %s\n", $diary->create_dt;
    printf "create_dt_utc: %s\n", $diary->create_dt_utc;
    printf "create_dt_jst: %s\n", $diary->create_dt_jst;

    # create_dt_jst から生えてるメソッドを変えても Data::Model は検知できないので
    # 他のエイリアスに値が反映されないので、意図的に set しなおしてます
    $diary->create_dt_jst(
        $diary->create_dt_jst->
            set_year(2012)->set_month(12)->set_day(21)->
                set_hour(12)->set_minute(0)->set_second(0)
    );
    print "\nchange ddate\n";

    printf "create_at    : %s\n", $diary->create_at;
    printf "create_dt    : %s\n", $diary->create_dt;
    printf "create_dt_utc: %s\n", $diary->create_dt_utc;
    printf "create_dt_jst: %s\n", $diary->create_dt_jst;
</pre>

<p>実行した結果は下記のようになります。</p>

<pre>
create_at    : 1261383942
create_dt    : 2009-12-21T08:25:42
create_dt_utc: 2009-12-21T08:25:42
create_dt_jst: 2009-12-21T17:25:42

change ddate
create_at    : 1356058800
create_dt    : 2012-12-21T03:00:00
create_dt_utc: 2012-12-21T03:00:00
create_dt_jst: 2012-12-21T12:00:00
</pre>

<p>create_dt_jst を変えただけなのに、 create_at, create_dt, create_dt_utc と全てが変更されてる事がわかりますね。</p>
</div>
<div class="section">
<h3> まとめ</h3>

<p>本日までで Data::Model のユニークな Column Sugar 並びに Alias Column についての紹介をしました。</p>
<p>他の ORM よりも柔軟にかつ簡単に役割を追加できる事がわかったとおもいます。</p>
<p>このあたりの機能はきちんとテストケースが書かれているので安心して利用できます。</p>


<p>という事で次回からはまた別の方面の紹介をします。</p>

</div>
]]></description>
      <dc:creator>yappo</dc:creator>
      <pubDate>Mon, 21 Dec 2009 08:32:01 GMT</pubDate>
      <category></category>
    </item>
    <item>
      <title>カラムの定義を使いまわす2</title>
      <link>http://perl-users.jp/articles/advent-calendar/2009/data-model/18.html</link>
      <description><![CDATA[<div class="section">
<h3> はじめに</h3>

<p>昨日は dann さんによる Memcached Protocol 向けのドライバの紹介を書いていただきました。 dann++</p>

<p>さて今日は一昨日の続きとして Column Sugar の続きを紹介しようと思います。</p>
</div>
<div class="section">
<h3> カラム定義を再利用可能に</h3>

<p>前回のカラム定義では、特定のスキーマ定義クラスの中で定義されてしまうので、他のクラスから使いたい時には使えません。</p>

<p>例えば MyDiary.pm の中に以下のような定義を行うと</p>

<pre>
column_sugar 'diary.id'
    => int => {
        required => 1,
        unsigned => 1,
    };

install_model diary => schema {
    key 'id';
    index user => [qw/ user_id id /];

    column 'diary.id';
    column 'user.id';
};
</pre>

<p>下記のようなエラーが発生して実行が停止しています。</p>

<pre>
$ perl -MMyDiary
Undefined column of 'user.id' at example/MyDiary.pm line 27
</pre>

<p>これは use MyBookmark してしまえば、 MyBookmark.pm の中で定義されている user.id が利用出来るのですが、もし MyBookmark で定義したテーブルを使わない時は無駄になってしまいます。</p>

<p>そういう時はカラム定義を外出ししてあげて、他のクラスからも再利用可能にしてあげる事ができます。</p>

<p>例えば Columns.pm といった形で以下のようにコードを書きます。</p>

<pre>
package Columns;
use strict;
use warnings;
use Data::Model::Schema sugar => 'mycolumns';

column_sugar 'user.id'
    => int => {
        required => 1,
        unsigned => 1,
    };

column_sugar 'diary.id'
    => int => {
        required => 1,
        unsigned => 1,
    };

column_sugar 'global.epoch'
    => int => {
        required => 1,
        unsigned => 1,
    };

1;
</pre>

<p>MyBookmark.pm と MyDiary.pm で定義したものを全部こっちに移動しました。</p>

<p>use Data::Model::Schema する時に sugar => sugar_name というオプションを与えてるのがポイントです。</p>

<p>このオプションを与える事で、複数の column_sygar 定義での定義名の衝突が防げるようになります。</p>
<p>デフォルトの sugar name としては default がしようされています。</p>

<p>sugar name をデフォルトから変更したので MyBookmark.pm と MyDiary.pm の use Data::Model::Schema に対してもオプションを変えてあげる必要があります。</p>

<pre>
package MyBookmark;
use strict;
use warnings;
use base 'Data::Model';
use Data::Model::Schema sugar => 'mycolumns';
use Data::Model::Mixin modules => ['+MyBookmark::Mixin::Count', 'FindOrCreate'];
use Columns;
</pre>

<pre>
package MyDiary;
use strict;
use warnings;
use base 'Data::Model';
use Data::Model::Schema sugar => 'mycolumns';
use Columns;
</pre>

<p>column sugar 定義クラスで mycolumns として定義したので、これを使う側のスキーマ定義クラスでも mycolumns をしていします。</p>

<p>またちゃんと use Columns; として Columns.pm をロードしておく事も重要です。</p>
</div>
<div class="section">
<h3> まとめ</h3>

<p>本日は、ある程度の規模の時に便利になってくる Column Sugar のライブラリ化についての紹介を行いました。</p>

<p>明日は Column Sugar の最後のトピックの Column Alias との連携について書きます。</p>
</div>
]]></description>
      <dc:creator>yappo</dc:creator>
      <pubDate>Mon, 21 Dec 2009 07:37:02 GMT</pubDate>
      <category></category>
    </item>
    <item>
      <title>Data::Model::Driver::Memcachedで超効率データ保存</title>
      <link>http://perl-users.jp/articles/advent-calendar/2009/data-model/17.html</link>
      <description><![CDATA[<div class="section">
<h3> はじめに</h3>
<p>YAPC::Asia 2009で、Data::Modelの話を聞いて以来、Data::Modelがとても気になっているdannです。</p>

<p>今回は、Data::Model::Driver::Memcachedについて紹介します。これは、Memcachedプロトコルを話すサーバーにデータを格納するためのDriverです。これの素晴らしいところは、空間効率を高めることを考えて設計されていて、かつアプリケーション側のコードは綺麗にかける工夫がなされているという点です。</p>

<p>では、どこにその工夫があるかをみていきましょう。</p>
</div>
<div class="section">
<h3> Data::Model::Driver::Memcachedの空間効率を含めるための工夫</h3>
<p>Data::Model::Driver:::Memcachedでは、以下のような流れでデータを保存します。</p>

<ul>
<li> データ全体のtable名をrenameし圧縮</li>
<li> データのvalueからkeyを削除</li>
<li> データのkey名をrenameし圧縮</li>
<li> データをMessagePack(defaultでは）で圧縮</li>
<li> 最後にMemcachedなどのKVSに保存</li>
</ul>

<p>このように、データを圧縮することで空間効率を高めています。</p>

<h4>  valueからのkey名の削除</h4>
<p>Data::Modelでは、テーブルに格納するデータをシリアライズして圧縮して、KVSに保存しますが、そのときにはvalueにはkey名は不要なのでkey名をvalueから削除しています。</p>
<p>strip_keysというオプションをdriverに設定することで利用できます。</p>

<pre>
package MyApp::Schema;
use strict;
use warnings;
use base 'Data::Model';

...

base_driver(
    Data::Model::Driver::Memcached->new(
        memcached => Cache::Memcached::Fast->new(
            { servers => [ { address => "localhost:11211" }, ], }
        ),  
        strip_keys => 1,
    )
);

...
</pre>

<h4> テーブル名の圧縮による省スペース化</h4>
<p>Data::Modelでは、キー名にテーブル名を含めるのですが、テーブル名をuという短い文字列に圧縮することができます。</p>
<p>schema_options model_name_realname => 'u'; で設定をしています。</p>

<pre>
package MyApp::Schema;
use strict;
use warnings;
use base 'Data::Model';

...

install_model user => schema {
    schema_options model_name_realname => 'u';
    key 'id';
    index 'name';
    columns qw/id name nickname/;
};

...
</pre>
</div>
<div class="section">
<h3> キー名の圧縮による省スペース化</h3>
<p>テーブル名だけでなく、キー名も圧縮することができます。</p>
<p>以下のようにキー名を数値にマッピングしておくことで、実行時にキー名を数値にリネームしてキーのサイズも圧縮することが出来ます。</p>
<p>schema_options column_name_renameでその設定をしています。</p>

<pre>
package MyApp::Schema;
use strict;
use warnings;
use base 'Data::Model';
...

install_model user => schema {
    key 'id';
    index 'name';
    columns qw/id name nickname/;
    schema_options column_name_rename => {
        id       => 1,
        name     => 2,
        nickname => 3,
    };  
};
...
</pre>
</div>
<div class="section">
<h3> シリアライザにMessagePackを使うことによる省スペース化</h3>
<p>シリアライザは選択できるようになっていて、DefaultはMessagePackという空間効率的なシリアライザを使われます。Data::Modelがこのようにシリアライズを切り替えられるようになっていることで、この効率化が実現できるところがポイントです。</p>

<pre>
base_driver(
    Data::Model::Driver::Memcached->new(
        memcached => Cache::Memcached::Fast->new(
            { servers => [ { address => "localhost:11211" }, ], }
        ),  
        serializer => 'Default',
    )   
);
</pre>


<p>どのように圧縮されるのかは、MessagePackの仕様に書かれているので、それをみるのがいいです。MessagePackがとても空間効率を意識して設計されていることがわかると思います。</p>
<p><a href="http://msgpack.sourceforge.jp/spec">http://msgpack.sourceforge.jp/spec</a></p>

<p>Data::Modelでは、テーブル名・キー名が短くなったデータに対して、さらにMessagePackで圧縮をしているため、かなり空間効率がいいと言えます。</p>
</div>
<div class="section">
<h3> アプリケーションのコードのシンプルさ</h3>
<p>以下のようにモデルを定義しておけば、アプリケーションのコードは、どのように圧縮されるのかを意識することなく、効率的にキー名及びデータの格納が行えます。</p>
<p>上記で述べたモデルの設定を実際にまとめてみましょう。</p>

<pre>
package MyApp::Schema;
use strict;
use warnings;
use base 'Data::Model';
use Data::Model::Schema;
use Data::Model::Driver::Memcached;
use Cache::Memcached::Fast;

base_driver(
    Data::Model::Driver::Memcached->new(
        memcached => Cache::Memcached::Fast->new(
            { servers => [ { address => "localhost:11211" }, ], }
        ),
        serializer => 'Default',
        strip_keys => 1,
    )
);
install_model user => schema {
    schema_options model_name_realname => 'u';
    key 'id';
    index 'name';
    columns qw/id name nickname/;
    schema_options column_name_rename => {
        id       => 1,
        name     => 2,
        nickname => 3,
    };
};

1;
</pre>

<p>Memcachedなどに格納するときには、以下のようにすることで格納できます。</p>
<p>特に利用する際には、データの圧縮などについて意識することなく利用することが出来ます。</p>

<p>セットする場合</p>
<pre>
$model->set(
    user => '1',
    {   name     => 'Warai',
        nickname => 'Meshi',
    }   
);
</pre>

<p>getする場合</p>
<pre>
my ($get) = $model->get( user => '1' );
</pre>
</div>
<div class="section">
<h3> 空間効率について</h3>
<p>Data::Modelでのオプション設定による空間効率について、以下のような簡単なテストコードで確認してみます。</p>

<pre>
use strict;
use warnings;
use Test::More;
use MyApp::Schema;
use Data::Dumper;

my $model = MyApp::Schema->new;
$model->set(
    user => '1',
    {   name     => 'Warai',
        nickname => 'Meshi',
    }   
);

my ($get) = $model->get( user => '1' );
is( $get->id,       '1',     'id' );
is( $get->name,     'Warai', 'name' );
is( $get->nickname, 'Meshi', 'nickname' );

done_testing;
</pre>

<p>実行して、telnet localhost 11211でmemcachedに接続して確認してみましょう。</p>

<h4>  Data::Modelのデフォルト状態で圧縮した状態</h4>
<h4> デフォルトの設定</h4>
<pre>
package MyApp::Schema;
use strict;
use warnings;
use base 'Data::Model';
use Data::Model::Schema;
use Data::Model::Driver::Memcached;
use Cache::Memcached::Fast;

base_driver(
    Data::Model::Driver::Memcached->new(
        memcached => Cache::Memcached::Fast->new(
            { servers => [ { address => "localhost:11211" }, ], }
        ),  
    )   
);
install_model user => schema {
    key 'id';
    index 'name';
    columns qw/id name nickname/;  
};

1;
</pre>

<p>telnetでmemcachedにつないでサイズの確認をしてみましょう。</p>
<pre>
STAT bytes 106
</pre>

<p>defaultでは、106バイトになりました。</p>


<h4> Data::Modelのオプションで圧縮した場合</h4>
<p>モデルは、以下のものとします。</p>
<pre>
package MyApp::Schema;
use strict;
use warnings;
use base 'Data::Model';
use Data::Model::Schema;
use Data::Model::Driver::Memcached;
use Cache::Memcached::Fast;

base_driver(
    Data::Model::Driver::Memcached->new(
        memcached => Cache::Memcached::Fast->new(
            { servers => [ { address => "localhost:11211" }, ], }
        ),
        serializer => 'Default',
        strip_keys => 1,
    )
);
install_model user => schema {
    schema_options model_name_realname => 'u';
    key 'id';
    index 'name';
    columns qw/id name nickname/;
    schema_options column_name_rename => {
        id       => 1,
        name     => 2,
        nickname => 3,
    };
};

1;
</pre>

<p>telnetでmemcachedにつないでサイズの確認をしてみましょう。</p>
<pre>
STAT bytes 69
</pre>

<p>圧縮した場合には69bytesになりました。</p>

<h4> サイズの比較</h4>
<p>Data::Modelのオプションを使って圧縮した場合には69bytes、デフォルト状態（Data::ObjectDriverのDriverなど）の場合では106bytesになりました。</p>
<p>したがって、今回のケースでは、圧縮した場合にデフォルト状態と比較して0.65倍となり、空間効率がかなりいいことが確認できました。</p>
</div>
<div class="section">
<h3> まとめ</h3>
<p>Data::Model::Driver::Memcachedは、KVSをデータストアとして使うために、空間効率を意識した設計がなされています。</p>
<p>具体的には、キーの値からの削除、テーブル名及びキー名の短縮、MessagePackによる圧縮、により空間効率を高める工夫をしています。</p>
<p>Data::Modelユーザーだけではなく、他のMemcachedプロトコルに対応したNoSQLサーバーを使われる方も、Data::ModelのDriverの実装を参考にされるのがいいのではないかと思います。</p>

<p>それでは、次回は再びyappoさんです。お楽しみに！</p>
</div>
]]></description>
      <dc:creator>dann </dc:creator>
      <pubDate>Sun, 20 Dec 2009 16:11:01 GMT</pubDate>
      <category></category>
    </item>
    <item>
      <title>カラムの定義を使いまわす</title>
      <link>http://perl-users.jp/articles/advent-calendar/2009/data-model/16.html</link>
      <description><![CDATA[<div class="section">
<h3> はじめに</h3>

<p>二日間に渡ってカラム定義の方法を紹介しました。</p>
<p>ここまでは、だいぶ普通の ORM と変わらない感じですね。</p>
<p>Inflate の定義とかはちょっと変わってるかもしれないですが、これは DBIx::MoCo からいただいた要素です。</p>

<p>今日からは Data::Model のユニークな機能としての Column Sugar について紹介しましょう。</p>
</div>
<div class="section">
<h3> What is Column Sugar?</h3>

<p>これは何をするものかというと、カラム定義の雛形を登録しておいて複数のカラムから雛形を作るという事ができます。</p>
<p>たとえば、レコードの作成日付を保存する created_on なんていうのは複数のテーブルで同じ定義をつかいたいものですから、共通化できると便利です。</p>

<p>他にも user_id なんかのカラムは複数のテーブルへカラム定義するケースが多いでしょう。特に正規化などをちゃんとするほど。</p>

<h4> 定義化をすると何がいいのか</h4>

<p>個人的な小さいアプリケーションや、開発のごく初期ではカラム定義の内容を細かく変えるケースがあるんじゃないかと思います。</p>
<p>そういう時は Schema Loader を使ったり ALTER TABLE しまくったり(もしくは DROP TABLE & CREATE TABLE)することでしょう。</p>

<p>まぁ、それでいいんですけど、開発の初期なんかは DROP TABLE & CREATE TABLE してた方が手っ取り早かったりすると思います。</p>
<p>で、この  DROP TABLE & CREATE TABLE しまくりの時に SQL で CREATE TABLE 文を書く、そして前述のいろんなテーブルで使われるような user_id の定義を変える。</p>
<p>といったときに Column Sugar で設定したカラム定義を一箇所書き換えるだけで済むので定義の変更漏れとかがなくなるというマニアックのシチュエーションがあります。</p>


<p>その他には、前述の通りにたような役割のカラムを一回だけ定義しておけば、テーブルのカラム定義では定義名を指定するだけで使いまわせるので楽になります。</p>

<p>たとえば Inflate の設定やら Default 値を CODE リファレンスで作ってるようなカラム定義で楽ができますよ。</p>
</div>
<div class="section">
<h3> 定義してみゆ</h3>

<p>さて、さっそくここで Column Sugar の定義でもしてみようと思います。</p>

<p>主に user テーブルの id カラムを作る人向けの定義は以下のようになります。</p>

<pre>
  column_sugar 'user.id'
      => int => {
          required => 1,
          unsigned => 1,
      };
</pre>

<p>普通のカラム定義の column の使い方とほとんど同じです。</p>
<p>column の代わりに column_sugar を使います。また、 column は install_model $tablename => {}; の中でしか利用できませんが、 column_sugar はどこで書いても OK です。</p>


<p>第一引数が "テーブル名.カラム名" という感じで、テーブル名とカラム名を . でつなげた形で指定します。</p>
<p>第二引数はカラム型。</p>
<p>第三引数がカラムの詳細な定義となります。</p>
<p>二と三は column と同じです。</p>

<p>重要なのが第一引数で "user.id" と定義した Column Sugar は、 user テーブルのカラム定義の中で使うと id というカラム名になり、 bookmark テーブルの中のカラム定義で使うと user_id というカラム名になります。</p>

<p>実際先程の column_sugar の定義を使ってみましょう。</p>

<pre>
install_model user => schema {
    key 'id';
    unique 'nickname';
    column 'user.id';
    columns qw/ nickname /;
};
install_model bookmark => schema {
    key [qw/ url_id user_id /];
    index 'user_id';
    column url_id => int => {};
    column 'user.id';
};
</pre>

<p>このような定義をすると以下のような CREATE TABLE 文に対応します。</p>

<pre>
CREATE TABLE user (
    id              INT             UNSIGNED NOT NULL,
    nickname        CHAR(255)      ,
    PRIMARY KEY (id),
    UNIQUE (nickname)
);
CREATE TABLE bookmark (
    url_id          INT            ,
    user_id         INT             UNSIGNED NOT NULL,
    create_at       INT             UNSIGNED NOT NULL,
    PRIMARY KEY (url_id, user_id)
);
CREATE INDEX user_id ON bookmark (user_id);
</pre>

<p>user.id は user テーブルの中だと user の部分を省略して id というカラム名になって、 user テーブル以外だと user_ の prefix がついて user_id というカラム名になってることがわかると思います。</p>
</div>
<div class="section">
<h3> 定義を上書きする</h3>

<p>さて、カラム定義の使い回しがし易いという column_sugar なんですが、定義を流用したいけども部分的に定義を変更したいケースがあると思います。例えば auto_increment をつけるとか。</p>

<p>そういった差分で上書きしたいときもちゃんと上書きして設定する事ができます。</p>

<p>例えば以下のようにcolumn_sugar の定義つけた unsigned => 1 を無効化したいときに以下のようかけます。</p>

<pre>
install_model user => schema {
    key 'id';
    unique 'nickname';
    column 'user.id' => {
        unsigned => 0
    };
    columns qw/ nickname /;
};
</pre>

<p>これは下記の SQL になります。</p>

<pre>
CREATE TABLE user (
    id              INT             NOT NULL,
    nickname        CHAR(255)      ,
    PRIMARY KEY (id),
    UNIQUE (nickname)
);
</pre>

<p>先程あった、 UNSIGNED の属性が消えてるのがわかると思います。</p>

<p>今度は、属性を追加したいときは単純に追加するだけです。</p>

<pre>
install_model user => schema {
    key 'id';
    unique 'nickname';
    column 'user.id' => {
        auto_increment => 1,
    };
    columns qw/ nickname /;
};
</pre>

<p>このように auto_increment 属性を追加した場合は</p>

<pre>
CREATE TABLE user (
    id              INTEGER         NOT NULL PRIMARY KEY,
    nickname        CHAR(255)      ,
    UNIQUE (nickname)
);
</pre>

<p>以上のように PRIMARY KEY 属性が追加されました。</p>

</div>
<div class="section">
<h3> カラム名を変更する</h3>

<p>さて、今度はカラム名だけを変えたいという時もあるでしょう。</p>

<p>そういう時は、以下のように定義すると名前だけかえられます。</p>
<pre>
# global.epoch という定義
column_sugar 'global.epoch'
    => int => {
        required => 1,
        unsigned => 1,
    };


install_model bookmark => schema {
    key [qw/ url_id user_id /];
    index 'user_id';
    column url_id => int => {};
    column 'user.id';

    # create_at を生の値でも使いたい
    column 'global.epoch' => 'create_at';
};
</pre>

<p>単純に、 column の第二引数に書き換えたいカラム名を指定するだけですみます。</p>
<p>上記の定義は下記の　 CREATE TABLE になります。</p>

<pre>
CREATE TABLE bookmark (
    url_id          INT            ,
    user_id         INT             UNSIGNED NOT NULL,
    create_at       INT             UNSIGNED NOT NULL,
    PRIMARY KEY (url_id, user_id)
);
</pre>

<p>じゃぁ名前変更と属性変更を同時にしたい場合はどうするか！？という時は、単純に書き換えたカラム名の次の引数に差し替えたい属性の HASH リファレンスを書けばいいだけです。</p>

<pre>
install_model bookmark => schema {
    key [qw/ url_id user_id /];
    index 'user_id';
    column url_id => int => {};
    column 'user.id';

    # create_at を生の値でも使いたい
    column 'global.epoch' => 'create_at';
        default => sub { time() },
    };
};
</pre>
</div>
<div class="section">
<h3> まとめ</h3>

<p>今日は Data::Model らしい Column Sugar に関して紹介しました。変に長文になってしまってあれですけど、明日は今日の続きを書きたいなとおもいます。</p>

</div>
]]></description>
      <dc:creator>yappo</dc:creator>
      <pubDate>Fri, 18 Dec 2009 19:58:01 GMT</pubDate>
      <category></category>
    </item>
    <item>
      <title>カラムの定義について2</title>
      <link>http://perl-users.jp/articles/advent-calendar/2009/data-model/15.html</link>
      <description><![CDATA[<div class="section">
<h3> はじめに</h3>

<p>さて、今日はカラム定義その2という事で昨日紹介してない事柄について紹介しましょう。</p>
</div>
<div class="section">
<h3> utf8 対応</h3>

<p>DBIC やら良くある最近の ORM では、カラムの値を flagged utf8 に inflate してくれるような仕組みがついてる事でしょう。</p>
<p>Yes, もちろん Data::Model にも仕組みがあります。</p>

<p>utf8_column と utf8_columns です。</p>

<pre>
    utf8_column 'column_name';
    utf8_columns qw/ column_name1 column_name2 column_name3 column_name4 /;
</pre>

<p>先日紹介した column と columns に utf8 flagged してくれる inflate を wrap したカラム定義をしてくれます。</p>

<p>例えば以下のような定義をすると</p>

<pre>
{
    package TestTable;
    use base 'Data::Model';
    use Data::Model::Schema;
    use Data::Model::Driver::DBI;

    my $driver = Data::Model::Driver::DBI->new(
        dsn => 'dbi:mysql:database=test'
    );
    base_driver $driver;

    install_model utf8_test => schema {
        key 'id';
        column 'id';
        utf8_column name
            => char => {
                required => 1,
                size     => 32,
            };
    };
}
</pre>

<p>次のように使えます。</p>

<pre>
use utf8;
use Encode;
my $db = TestTable->new;

$db->set( utf8_test => 1 => { name => '大沢和宏' } );
my $row = $db->lookup( utf8_test => 1 );
if (Encode::is_utf8($row->name)) {
    print $row->name . "\n";
}
</pre>

<p>$row->name には普通に　utf8 flag が立つのできちんと名前が表示されます。</p>

</div>
<div class="section">
<h3> utf8 column に inflate をかける</h3>

<p>utf8_column したカラムに inflate 処理を追加したい事もあるでしょう。</p>
<p>それも、もちろんできます。</p>

<pre>
    utf8_column foo
        => char => {
            inflate => sub {
                my $val = shift; # utf8 flag 付きで値が入る
            },
            deflate => sub {
                return $utf_flagged_val; # utf8 flag 付きの値を返す

            },
        };
</pre>

<p>のような感じで、 inflate する CODE リファレンスには utf8 flag を立てた状態の値が渡されます。</p>
<p>deflate で return する値はもちろん flagged utf8 な値である必要があります。</p>

<p>例として先程の　TestTable の utf8_test テーブルに対して utf8_column な inflate 定義をします。</p>
<p>ついでなので alias_column でエイリアス張ったカラムに inflate 設定しましょう。</p>

<pre>
        utf8_column name
            => char => {
                required => 1,
                size     => 32,
            };

        alias_column name => obj => {
            inflate => sub { Name->new($_[0]) },
            deflate => sub { $_[0]->name },
        };
</pre>

<p>Name は以下のようなコードです。</p>

<pre>
    package Name;
    sub new {
        my($class, $name) = @_;
        bless \$name, $class;
    }
    sub name { ${ $_[0] } }
</pre>

<p>実際に動かしてみましょう。</p>

<pre>
use utf8;
use Encode;
my $db = TestTable->new;

$db->set( utf8_test => 1 => { name => '大沢和宏' } );
my $row = $db->lookup( utf8_test => 1 );
if (Encode::is_utf8($row->name)) {
    print $row->name . "\n";
}
if (Encode::is_utf8($row->obj->name)) {
    print $row->obj->name . "\n";
}

# update してみる
$row->name('ねこかくたろう');
if (Encode::is_utf8($row->name)) {
    print $row->name . "\n";
}
if (Encode::is_utf8($row->obj->name)) {
    print $row->obj->name . "\n";
}
$row->update;

my $row2 = $db->lookup( utf8_test => 1 );
if (Encode::is_utf8($row->name)) {
    print $row->name . "\n";
}
if (Encode::is_utf8($row->obj->name)) {
    print $row->obj->name . "\n";
}
</pre>

<p>結果は以下のようになっています。</p>

<pre>
大沢和宏
大沢和宏
ねこかくたろう
ねこかくたろう
ねこかくたろう
ねこかくたろう
</pre>

<p>ちゃんと decode 処理してないで print するので Wide character とか出ますが、 utf8 flag がきちんと立ってることがわかると思います。</p>
</div>
<div class="section">
<h3> まとめ</h3>

<p>今日は utf8 column の使い方と inflate との組み合わせについて解説しました。</p>

<p>カラム定義まわりは、まだちょびっとだけ続くぞ。</p>
</div>
]]></description>
      <dc:creator>yappo</dc:creator>
      <pubDate>Thu, 17 Dec 2009 07:48:01 GMT</pubDate>
      <category></category>
    </item>
    <item>
      <title>カラムの定義について</title>
      <link>http://perl-users.jp/articles/advent-calendar/2009/data-model/14.html</link>
      <description><![CDATA[<div class="section">
<h3> はじめに</h3>

<p>いままで、 mixin だの row class の拡張だの inflate だのと書いてきましたが、大事な事を書いていませんでした。</p>
<p>そうです、テーブルのカラムの定義についてです。</p>

<p>ということで今日から数回に分けてカラムの定義方法を書いていきます。</p>
</div>
<div class="section">
<h3> 超簡単なカラム</h3>

<p>Data::Mode;;Schema を use すると、スキーマ定義に必要な DSL がインストールされる旨はだいぶ初回の時に説明しましたが、カラムする為の DSL も各種インストールされています。</p>

<p>column と columns が良く使われる定義で、それぞれ1つのカラム定義か複数のカラム定義を行えます。</p>

<p>使い方はそれぞれ</p>

<pre>
    column 'column_name';
    columns qw/ column_name1 column_name2 column_name3 column_name4 /;
</pre>

<p>となります。</p>
<p>この使い方は、カラム名だけ引数に与えて与えた引数でのカラム名定義を行ってくれます。</p>
</div>
<div class="section">
<h3> カラム定義を真面目にする</h3>

<p>上の簡単な使い方だとカラムの型やら具体的な定義が書かれてないので NOT NULL 制約があるのか何なのか良くわかりません。</p>
<p>そこで、 DBIC とかの良くあるちゃんとした定義を書きます。</p>

<p>これには先の column を利用します。 columns では詳細な定義はできません。</p>

<pre>
    column column_name
        => int => $options;
</pre>

<p>このように書きます。</p>
<p>第一引数はカラム名。</p>
<p>第二引数はカラム型。</p>
<p>第三引数がカラムの詳細な定義となります。</p>

<p>カラムの詳細な定義とは、 NOT NULL 制約やら DEFAULT の設定やカラムのサイズなどです。</p>
<p>具体的に定義可能なパラメータは <a href="http://search.cpan.org/dist/Data-Model/lib/Data/Model/Schema.pm#COLUMN_OPTIONS">http://search.cpan.org/dist/Data-Model/lib/Data/Model/Schema.pm#COLUMN_OPTIONS</a> に記載してあります。</p>

<p>例えば</p>
<pre>
    name CHAR(16) NOT NULL
</pre>
<p>という定義をしたいときは</p>
<pre>
    column name
        => char => {
            required => 1,
            size     => 16,
        };
</pre>

<p>という定義を行います。</p>
</div>
<div class="section">
<h3> DEFAULT 値</h3>

<pre>
    name CHAR(16) DEFAULT 'Yappo'
</pre>

<p>というような DEFAULT の定義をするには</p>

<pre>
    column name
        => char => {
            default  => 'Yappo',
            size     => 16,
        };
</pre>

<p>と書けば良いだけなのですが、例えば特定の計算結果(YUID とか)を DEFAULT として入れておきたい場合もあるかと思います。</p>
<p>そんな時は default => sub {} のように CODE リファレンスを書いておくと INSERT するタイミングで CODE リファレンスを実行してくれて、その戻り値を INSERT する用になります。</p>

<pre>
    my @names = qw/ Yappo nekokak kan /;
    column name
        => char => {
            default  => sub {
                @names[rand(@names)]
            },
            size     => 16,
        };
</pre>

<p>とかやれば、 Yappo, nekokak, kan のいづれかが DEFAULT として利用されます。</p>
</div>
<div class="section">
<h3> AUTO INCREMENT</h3>

<p>さて、いよいよ auto increment の出番です。</p>
<p>といっても、とても簡単で auto_increment => 1 するだけです。</p>

<pre>
    column id
        => int => {
            required       => 1,
            unsigned       => 1,
            auto_increment => 1,
        };
</pre>

<p>これだけなんで、とっても簡単ですね。この定義で SQLite と MySQL ともに動きます。</p>
<p>ちなみに unsigned => 1 ってのは INT UNSIGNED ってするだけです。</p>

<p>さて、 MySQL の MyISAM, BDB テーブルでは複合 primary key の二つ目のカラムに AUTO_INCREMENT を指定できます。</p>
<p><a href="http://dev.mysql.com/doc/refman/5.1/en/example-auto-increment.html">http://dev.mysql.com/doc/refman/5.1/en/example-auto-increment.html</a></p>
<p>一つ目の値のグーループに対する auto increment をつけてくれるのですが Data::Model で以下のようにスキーマ定義を行ってテーブルを作れば対応可能です。</p>

<pre>
{
    package TestTable;
    use base 'Data::Model';
    use Data::Model::Schema;
    use Data::Model::Driver::DBI;

    my $driver = Data::Model::Driver::DBI->new(
        dsn => 'dbi:mysql:database=test'
    );
    base_driver $driver;

    install_model auto_increment_test => schema {
        key [qw/ id entry_id /];
        column id
            => int => {
                required => 1,
                unsigned => 1,
            };
        column entry_id
            => int => {
                auto_increment => 1,
                required       => 1,
                unsigned       => 1,
            };
    };
}
print join(";\n", TestTable->as_sqls, '');
</pre>

<p>使い方は以下の通りで</p>

<pre>
my $db = TestTable->new;

$db->set( auto_increment_test => 1 );
$db->set( auto_increment_test => 1 );
$db->set( auto_increment_test => 2 );
$db->set( auto_increment_test => 1 );
$db->set( auto_increment_test => 1 );
$db->set( auto_increment_test => 2 );

my $itr = $db->get('auto_increment_test');
while (<$itr>) {
    printf "id = %d, entry_id = %d\n", $_->id, $_->entry_id;
}
</pre>

<p>結果は下記のようになります。</p>

<pre>
my $db = TestTable->new;

$db->set( auto_increment_test => 1 );
$db->set( auto_increment_test => 1 );
$db->set( auto_increment_test => 2 );
$db->set( auto_increment_test => 1 );
$db->set( auto_increment_test => 1 );
$db->set( auto_increment_test => 2 );

my $itr = $db->get('auto_increment_test');
while (<$itr>) {
    printf "id = %d, entry_id = %d\n", $_->id, $_->entry_id;
}
</pre>
</div>
<div class="section">
<h3> まとめ</h3>

<p>本日はカラム定義の詳細を書きました。</p>
<p>明日は他のカラム定義を見ていきます。</p>


</div>
]]></description>
      <dc:creator>yappo</dc:creator>
      <pubDate>Wed, 16 Dec 2009 03:40:03 GMT</pubDate>
      <category></category>
    </item>
    <item>
      <title>カラムにエイリアスを張る</title>
      <link>http://perl-users.jp/articles/advent-calendar/2009/data-model/13.html</link>
      <description><![CDATA[<div class="section">
<h3> はじめに</h3>

<p>さて、先日までは Inflate の使い方を見てきました。</p>
<p>まだ utf8_column の話しはしてませんが後回しにして、 Inflate と一緒に使うと便利なエイリアスカラムの機能を紹介します。</p>

<pre>
    alias_column original_name => alias_name => { ... };
</pre>

<p>のように使います。</p>
<p>第一引数がエイリアス元となるカラムの名前。</p>
<p>第二引数がエイリアスしたカラムの名前。</p>
<p>第三引数が設定です。</p>

<p>もちろん設定など何も書かずに単純にエイリアスとしても使えるんですが、あまり意味がありません。</p>
<p>一番美味しい使い方としては、オリジナルカラムには Inflate の設定を行わずにオリジナルのまま利用できるようにしつつ、エイリアスカラム側に Inflate の設定をして、オリジナルのデータと Inflate のデータを両方扱えるようにするといった事をする時です。</p>
</div>
<div class="section">
<h3> 定義</h3>

<p>さて実際に、定義してみましょう。先日からいじっている bookmark テーブルの　create_at カラムに対してこの美味しいエイリアスを仕込みます。</p>

<pre>
    # create_at を生の値でも使いたい
    column create_at => int => {
        required => 1,
        unsigned => 1,
        default => sub { time() },
    };
    # Inflate するのはエイリアスで
    alias_column create_at => create_dt => {
        inflate => 'DateTime',
    };
</pre>

<p>単純に create_at の定義から Inflate の設定を抜いて alias_column で create_dt というエイリアスカラムを張って、そこのカラムに　DateTime の Inflate 定義を設定しています。</p>
</div>
<div class="section">
<h3> 使ってみる</h3>

<p>さて、先日までのサンプルスクリプトを create_dt に対応させて利用例を見てみましょう。</p>

<pre>
    $bookmark->set( bookmark => [1, 1] );
    my $row = $bookmark->lookup( bookmark => [1, 1] );
    print "--- insert\n";
    print $row->create_at . "\n";
    print $row->create_dt . "\n";

    my $dt = DateTime->new( year => 1978, month => 3, day => 20 );
    $row->create_dt( $dt );
    print "--- update 1978/3/20\n";
    print $row->create_at . "\n";
    print $row->create_dt . "\n";
    $row->update;

    my $row2 = $bookmark->lookup( bookmark => [1, 1] );
    print "--- lookup 1978/3/20\n";
    print $row2->create_at . "\n";
    print $row2->create_dt . "\n";
    $row2->create_at( 0 );
    print "--- update 0\n";
    print $row2->create_at . "\n";
    print $row2->create_dt . "\n";
    $row2->update;

    my $row3 = $bookmark->lookup( bookmark => [1, 1] );
    print "--- lookup 0\n";
    print $row3->create_at . "\n";
    print $row3->create_dt . "\n";
</pre>

<p>結果は以下のとおりです。</p>

<pre>
--- insert
1260772604
2009-12-14T06:36:44
--- update 1978/3/20
259200000
1978-03-20T00:00:00
--- lookup 1978/3/20
259200000
1978-03-20T00:00:00
--- update 0
0
1970-01-01T00:00:00
--- lookup 0
0
1970-01-01T00:00:00
</pre>

<p>create_at, create_dt メソッドそれぞれに値を入れたら即座にお互いの値も変更されてるのがわかると思います。</p>

<p>先日 DateTime の deflate 定義として数値と DateTime オブジェクト両方受け入れられるようにした時に epoch time を直接いれてしまうと、それいこう DateTime オブジェクトで取得出来ない問題がありましたが、エイリアスカラムを使うと常に値が入れられたタイミングで同期されるので安心です。</p>
</div>
<div class="section">
<h3> まとめ</h3>

<p>数日連続して Inflate 関連の話題を書きました。</p>
<p>alias_column は、元データと加工後のデータがそれぞれ必要なときに便利です。</p>

<p>ということで次回はまた別のジャンルのネタにしようとおもいます。</p>
</div>
]]></description>
      <dc:creator>yappo</dc:creator>
      <pubDate>Mon, 14 Dec 2009 06:49:01 GMT</pubDate>
      <category></category>
    </item>
    <item>
      <title>Inflate の定義を使いまわす</title>
      <link>http://perl-users.jp/articles/advent-calendar/2009/data-model/12.html</link>
      <description><![CDATA[<div class="section">
<h3> はじめに</h3>

<p>昨日は Inflate/Deflate を設定して利用する事を紹介しました。</p>
<p>昨日の方法では、 column ごとにコードリファレンスを書いていって、別の column で同じ設定を使いたい時にコピペプログラミングになってしまうので微妙です。</p>
<p>まぁ、本当にひとつのカラムで使いたければ昨日の方法でも良いんですが create_at を DateTime のオブジェクトにしたい時は面倒です。</p>

<p>そこで本日は、そういった面倒な事を回避出来る Inflate の設定を紹介します。</p>
</div>
<div class="section">
<h3> inflate_type の設定</h3>

<p>使い回し可能な Inflate の設定をするには Data::Model::Schema::Inflate が export する inflate_type 関数を利用します。</p>
<p>昨日の DateTime の Inflate 設定を行うには以下のように書きます。</p>

<pre>
    # inflate setup
    use Data::Model::Schema::Inflate;
    use DateTime;

    inflate_type DateTime => {
        inflate => sub {
            DateTime->from_epoch( epoch => $_[0] );
        },
        deflate => sub {
            ref($_[0]) && $_[0]->isa('DateTime') ? $_[0]->epoch : $_[0];
        },
    };
</pre>

<p>inflate_type の第一引数に定義名を書き、第二引数に inflate, defulate を key にして value に CODE リファレンスを書いた HASH リファレンスを渡します。</p>
<p>HASH-ref の中身の inflate と deflate の中身は全く変わっていません。</p>

<p>これを、利用するには column の定義の中の inflate に対して定義名を渡すだけです。</p>


<pre>
    column create_at => int => {
        required => 1,
        unsigned => 1,
        default => sub { time() },
        inflate => 'DateTime',
    };
</pre>

<p>ちょっと簡潔になりましたね。</p>

<p>ちなみに、組み込みの inflate 定義として URI と Hex があります。</p>
<p>定義は以下のように成っています。</p>

<pre>
# in Data::Model::Schema::Inflate
my %INFLATE = (
    inflate => {
        URI  => sub { URI->new($_[0]) },
        Hex  => sub { unpack("H*", $_[0]) },
    },
    deflate => {
        URI  => sub { $_[0]->as_string },
        Hex  => sub { pack("H*", $_[0]) },
    },
);
</pre>
</div>
<div class="section">
<h3> 使ってみる</h3>

<p>利用方法は昨日のコピペですが以下のようになります。</p>

<p>以下のように使います。</p>

<pre>
    $bookmark->set( bookmark => [1, 1] );
    my $row = $bookmark->lookup( bookmark => [1, 1] );
    print $row->create_at . "\n";

    my $dt = DateTime->new( year => 1978, month => 3, day => 20 );
    $row->create_at( $dt );
    print $row->create_at . "\n";
    $row->update;

    my $row2 = $bookmark->lookup( bookmark => [1, 1] );
    print $row2->create_at . "\n";
    $row2->create_at( 0 );
    print $row2->create_at . "\n";
    $row2->update;

    my $row3 = $bookmark->lookup( bookmark => [1, 1] );
    print $row3->create_at . "\n";
</pre>

<p>結果は下記の通り。</p>

<pre>
(この行は実行した時間が入ります)
1978-03-20T00:00:00
1978-03-20T00:00:00
0
1970-01-01T00:00:00
</pre>
</div>
<div class="section">
<h3> まとめ</h3>

<p>本日は再利用可能な Inflate に関して説明しました。</p>
<p>明日は Inflate と組み合わせると便利になる昨日の紹介をします。</p>
</div>
]]></description>
      <dc:creator>yappo</dc:creator>
      <pubDate>Mon, 14 Dec 2009 05:51:02 GMT</pubDate>
      <category></category>
    </item>
    <item>
      <title>Inflate について</title>
      <link>http://perl-users.jp/articles/advent-calendar/2009/data-model/11.html</link>
      <description><![CDATA[<div class="section">
<h3> はじめに</h3>

<p>今回から数回にわたって Inflate にについて書きます。</p>

<p>Data::Model でも、他の ORM と同様に Inflate/Deflate が用意されています。</p>

<p>これは、データベースから値を取得した時に自動的にオブジェクトのインスタンスを作ってユーザがオブジェクトごしに値にアクセス出来るようになります。</p>
<p>ぎゃくに、オブジェクトを渡して INSERT すると自動的にシリアライズしてテーブルに保存します。</p>
</div>
<div class="section">
<h3> スキーマ定義</h3>

<p>さて、実際に Inflate を使ったカラムを作ってみましょう。</p>

<p>MyBookmark の bookmark テーブルは、ブックマークした時間が保存出来ないので、保存出来るようにします。</p>
<p>そして、ブックマークした日時を DateTime のオブジェクトとして扱えるようにしましょう。</p>
<p>データベースには epoch time を保存しておいて、通常は DateTime として扱いたい場合を書いてみます。</p>

<p>以下のようなコードになります。</p>
<pre>
    column create_at => int => {
        required => 1,
        unsigned => 1,
        default => sub { time() },
        inflate => sub {
            DateTime->from_epoch( epoch => $_[0] );
        },
        deflate => sub {
            ref($_[0]) && $_[0]->isa('DateTime') ? $_[0]->epoch : $_[0];
        },
    };
</pre>

<p>INSERT 時に値を省略出来るように　default の定義をしています。今回は INSERT した時の time の値をそのまま入れます。</p>

<p>inflate の第一引数はデータベースから取り出したままの値です。</p>
<p>今回は epoch time が入ってくるので DateTime オブジェクトにして返します。</p>

<p>deflate の第一引数は row オブジェクトで利用している、そのカラムの値です。</p>
<p>inflate と比べて若干ややこしいですが、 $row->create_at( time() ) などと DateTime ではなく直接 epoch time を入れられる事に対応するためにこうなってます。</p>
</div>
<div class="section">
<h3> 使ってみる</h3>

<p>使い方は特に変わった事をする必要はありません。</p>
<p>create_at が DateTime オブジェクトを返すという事と、 create_at に epoch time と DateTime を入れられるという事位です。</p>

<p>以下のように使います。</p>

<pre>
    $bookmark->set( bookmark => [1, 1] );
    my $row = $bookmark->lookup( bookmark => [1, 1] );
    print $row->create_at . "\n";

    my $dt = DateTime->new( year => 1978, month => 3, day => 20 );
    $row->create_at( $dt );
    print $row->create_at . "\n";
    $row->update;

    my $row2 = $bookmark->lookup( bookmark => [1, 1] );
    print $row2->create_at . "\n";
    $row2->create_at( 0 );
    print $row2->create_at . "\n";
    $row2->update;

    my $row3 = $bookmark->lookup( bookmark => [1, 1] );
    print $row3->create_at . "\n";
</pre>

<p>結果は下記の通り。</p>

<pre>
(この行は実行した時間が入ります)
1978-03-20T00:00:00
1978-03-20T00:00:00
0
1970-01-01T00:00:00
</pre>

<p>0 になってる部分は、 create_at に 0 を入れたため、そのまま出てきてしまっています。</p>
<p>あくまでもデータベスから値を出すときに inflate 処理されているという事です。</p>

<p>これは、回避策がありますがまた今度の機会に。</p>
</div>
<div class="section">
<h3> まとめ</h3>

<p>本日は Inflate を使ったカラムの作り方を扱いました。</p>

<p>明日はもう少し Inflate について掘り下げます。</p>
</div>
]]></description>
      <dc:creator>yappo</dc:creator>
      <pubDate>Mon, 14 Dec 2009 05:51:02 GMT</pubDate>
      <category></category>
    </item>
  </channel>
</rss>
