読者です 読者をやめる 読者になる 読者になる

あと味

たくさん情報を食べて、たくさん発信すると、あとになって味わい深い。

MTオブジェクトの複製を作るツールスクリプトを作った

Movable Type使ってる時に、たまに、ページングのテストや負荷テスト等をしたい時に、記事を大量にコピーしたい時があって、これまで下記のようなスクリプトを叩いたりしてコピーを作ってました。

これをMTオブジェクト全般に広げても良いかなと思ったので、ObjectCloneというプラグインを作りました。

ObjectClone プラグイン

usage のままですが、下記のように使います。

USAGE: perl tools/object-clone --model=NAME --orig_id=NUM [OPTION]

Requires:
  -m, --model=NAME      Model name (object datasource) of object to make clone.
  -o, --orig_id=NUM     Original object id clone object.

Options:
  -a, --amount=NUM      Amount of clones to make. Default 1.
  -d, --debug           Output debug info to STDERR.
  -h, --help            Show help.
  -r, --redefine=PAIR   Redefine column value with column key and column value pair.
  -u, --usage           Show usage.

Examples:
  # Make clones of 100 objects of MT::Entry from original entry that entry_id is 1.
  $ tools/object-clone --model=entry --orig_id=1 --amount=100

  # Make a clone of MT::Blog, and redefine name, site_path and site_url.
  $ tools/object-clone -m blog -o 2 -r 'name=Clone Blog' -r 'site_path=/path/to/site-path' -r 'site_url=/::/clone-blog/'

基本は、モデル名とコピー元のオブジェクトのIDを、それぞれ、--modelオプションと--orig_id オプションで指定してクローンを作ります。

クローンを複数作る場合は、作る数を--amountオプションで指定します。

他には、単に丸々コピーするだけではなくて、一部、値を変更する場合は、--redefineオプションで、フィールド名とその値を=で区切って指定します。

具体的には、Aというブログの記事を、Bというブログにコピーしたいような時などに、下記のように指定します。

$ perl tools/object-clone --model entry --orig_id 1 --amount 10 --redefine blog_id=3

コマンドラインオプションの単語が適切かどうかは微妙な感じもしますが、とりあえず必要最低限なオプションだけ用意しました。

値の変更が必要なケース

DB上の制約はないにも関わらず、実際にはMT::Entryのベースネームだとか、CustomFields::Fieldのタグ名は、値が重複しないようにCMS上で制限がかかっていたり、バックグラウンドでの値の生成の実装があったりします。

その辺の対応はオブジェクト毎に必要な感じなので、ObjectClone::Patcherとそのサブクラスに切り出しています。

単体で使うことはほぼないと思いますが、下記のような感じで使います。

use strict;
use warnings;

use lib qw(lib extlib plugins/ObjectClone/lib);

use MT;
use ObjectClone::Patcher;
use ObjectClone::Patcher::MT::Entry;

my $mt = MT->instance;
my $orig_obj = MT::Entry->load(1) or die MT::Entry->errstr;
my $new_obj = $orig_obj->clone;
my $patcher = ObjectClone::Patcher->model('entry')->new($new_obj, $orig_obj);
$patcher->remove_patch(basename => \&ObjectClone::Patcher::MT::Entry::basename)
        ->add_patch(basename => sub { 'new-basename' })
        ->add_patch(convert_breaks => sub { 'markdown' })
        ->apply_patch;
$new_obj->save or die $new_obj->errstr;

デフォルトで下記のPatcherクラスを用意しています。

  • ObjectClone::Patcher::MT::Entry
  • ObjectClone::Patcher::MT::Category
  • ObjectClone::Patcher::MT::Author
  • ObjectClone::Patcher::CustomFields::Field

とは言っても実際に実装があるのは、ベースネームをユニークにする処理くらいです。

独自オブジェクトをtools/clone-objectで生成したいときなどは、上記のPatcherクラスを追加すれば対応ができます。( ObjectClone::Patcher::{オブジェクト名} )

後は、apply_patchメソッドで、パッチを適用する前に、before_apply_patchというコールバックが呼ばれるので、デフォルトのパッチ処理を無効にしたり、それに加えて何かの処理を実行したりする時は、コールバックプラグインで拡張できます。

コールバックプラグインのサンプル

下記のようにコールバックを使ったプラグインが書けます。

id: ObjectCloneCallback
key: object-clone-callback
name: ObjectCloneCallback
callbacks:
  ObjectClone::Patcher::MT::Entry::before_apply_patch: |
    sub {
      my ($cb, $patcher) = @_;
      $patcher->add_patch('basename', sub {
        my ($new_obj, $orig_obj) = @_;
        'new-basename';
      });
    }

まとめ

主に開発ツールとして作ったので、開発時には役に立つ気がしています。

シェルスクリプトと組み合わせたりするのも良いかもしれません。

ほなの。