おそらくはそれさえも平凡な日々

Teng::Plugin::RowObjectCreationSwitcherが便利な件

https://metacpan.org/release/Teng-Plugin-RowObjectCreationSwitcher

@tsucchi さんに上げてもらったのでその話。

追記

注意

tokuhiromに意見をもらって0.02でインターフェースが変わりました。

  • temporary_suppress_row_objects_guard( 1 / 0 ) を新設
  • (dis|en)able_row_object を廃止
  • ガードオブジェクト受け取らないとエラーになるように

「こういう$tengみたいなグローバルなオブジェクトの内部を一時的に変えるみたいなのはやらないほうがいいですね!」

と、tokuhiromに言われており、実際そうなので、やるなら自己責任で気をつけて使いましょう。

言われてみると、$teng->suppress_row_objectsがそもそもrwなのが怖い感じがしますね。

本編(修正部分反映済み)

ORM好きのRowオブジェクト大好きっ子としては、当然Tengを使う時もRowオブジェクトをバリバリ使うわけです。

Tengにはsuppress_row_objectsっていうアクセサがあって、Rowオブジェクトじゃなくて単なるHashRefを返したい場合は、これが真値に切り替えたり、そもそもRowオブジェクトとか使わないよ!とか言う生大好きな人とかはこれを常に1にしたりしているわけです。

僕としては、概ねRowオブジェクト使いたいけど、たまにHashRefでいいかなって時もあったり、逆にAPIのレスポンスをとかさくっと作りたいってときとかはそっちのほうが都合が良かったりすることもあります。

例えばあるユーザーの情報をHashRefで受け取りたいとして、それをどう実現するか。

sub get_user_by_id {
    my ($teng, $id) = @_;
    $teng->suppress_row_objects(1);
    my $row = $teng->single(user => {id => $id});
    $teng->suppress_row_objects(0);
    return $row;
}

こうでしょうか?いいえ、違います。これだと、suppress_row_objectsのもとの値が0だとは限らないからです。

sub get_user_by_id {
    my ($teng, $id) = @_;
    my $orig = $teng->suppress_row_objects;
    $teng->suppress_row_objects(1);
    my $row = $teng->single(user => {id => $id});
    $teng->suppress_row_objects($orig);
    return $row;
}

ではこれでよいのでしょうか?実はこれでもまだダメです。$teng->singleの部分で例外が発生した場合に、$tengにもとの値を戻してやることが出来ません。

sub get_user_by_id {
    my ($teng, $id) = @_;
    my $orig = $teng->suppress_row_objects;
    $teng->suppress_row_objects(1);
    my $row = eval {
        $teng->single(user => {id => $id});
    };
    my $err = $@;
    $teng->suppress_row_objects($orig);
    die $err if $err;
    return $row;
}

まあこれで大体okでしょう。ここでの一つ注意点はsuppress_row_objectsに値をセットする前にmy $err = $@しているところ。なぜかというと、万が一、suppress_row_objectsの中でevalが呼ばれていたりすると、そこで$@がクリアされてしまうからです。

はい、ちゃんと実装するのは非常にタルいですね。そこで、Teng::Plugin::RowObjectCreationSwitcherの出番です。導入はほかのPlugin同様です。

package MyApp::DB;
use parent 'Teng';
__PACKAGE__->load_plugin('RowObjectCreationSwitcher');

こうしておいて、後はさっきの関数は以下のように書けるようになります。

sub get_user_by_id {
    my ($teng, $id) = @_;

    my $guard = $teng->temporary_suppress_row_objects_guard(1);
    return $teng->single(user => {id => $id});
}

超シンプルになりました。RowObjectCreationSwitcherを導入すると、temporary_suppress_row_objects_guardというメソッドが$tengに生えます。戻り値がガードオブジェクトを受け取り、スコープを抜ける瞬間に元の値に戻してくれるという寸法です。

ということで超便利ですね。ほとんど同じインターフェースでモジュール作ろうと思っていたんですけど、ふと@tsucchiさんが作っていたのを思い出して、探してみたらビンゴだったので微調整をお願いしてリリースしてもらった次第です。

実は、

sub get_user_by_id {
    my ($teng, $id) = @_;

    local $teng->{suppress_row_objects} = 1;
    return $teng->single(user => {id => $id});
}

ってやるのも同じだったりするんですが、まあなんか、オブジェクトのHashRefの中身に直接アクセスするのもどうなのかなーって思ったりもして、ちゃんとしたインターフェースを提供するとなるとこういう感じになるのかなーと思うので良いんじゃないかと思います。

自己責任!

created at
last modified at

2013-10-02T21:44:16+0900

comments powered by Disqus