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

Class::Component の run_hook にPlaggerライクな rule をかます(その2)

perl

昨日の続き。
今回は新たに定義したRuleHookアトリビュートを使ってruleをかませました。こっちのほうが、run_hookを上書きすることも無く、フレームワークとして綺麗ですね。
他にもいい方法あるかな?

変更点は…

package Naoya;
use strict;
use warnings;
use base qw/Class::Component/;

__PACKAGE__->load_plugins(qw/ Jitensya /);

1;

非常にシンプルになりました。run_hookのオーバーライドはやめて、Autocall::InjectMethodコンポーネントもこの例では必要ないので消しました。

package Naoya::Plugin::Jitensya;
use strict;
use warnings;
use base 'Naoya::Plugin';

sub ruled : RuleHook('bell') {
    my($self, $context, $args) = @_;
    print $self->config->{sound}, "\n";
}

sub normal : Hook('force_bell') {
    my($self, $context, $args) = @_;
    print $self->config->{sound}, "\n";
}

1;

rule_hookを無くした代わりに、bellフックのアトリビュートをRuleHookに変えています。

そのほかのファイルもruleの取り回し関係のコードは不必要になった為、削除してます。


でキモとなるアトリビュートのコードがこれ。

package Naoya::Attribute::RuleHook;
use strict;
use warnings;
use base 'Class::Component::Attribute';

sub register {
    my $class = shift;
    my($plugin, $c, $method, $value, $code) = @_;
    $c->register_hook( $value => { plugin => $plugin, method => $method } );

    no strict 'refs';
    no warnings 'redefine';

    *{ ref($plugin) . "::$method" } = sub {
        my($self, $context, $args) = @_;
        return unless $self->rule->dispatch($self, $args);
        $code->($self, $context, $args);
    };
}

1;

フックメソッドを再定義してます。rule通らなかったらリターン、rule通ったらオリジナルのコードを実行。

これで前回と同じテストは通りました。けど、もっと厳密にテストすると実は不具合あるかもしんない。


追記:
メソッドのredefineはしないほうがやっぱいい。内部でコード(リファレンス)をキャッシュしてる関係で、意図しない動きが発生する場合がある。
コンポーネントでオレオレrun_hookを実装するのがベストかな。