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の中身に直接アクセスするのもどうなのかなーって思ったりもして、ちゃんとしたインターフェースを提供するとなるとこういう感じになるのかなーと思うので良いんじゃないかと思います。
自己責任!