日向夏特殊応援部隊

俺様向けメモ

Moose::Cookbook::Recipe6 - augment, inner -

次はネストする呼び出しである augment, inner についてです。

ソースコード

package Document::Page;

use Moose;
use Perl6::Say;

has 'body' => ( is => 'rw', isa => 'Str', default => sub { '' } );

sub create {
    my $self = shift;
    $self->open_page;
    inner();
    $self->close_page;
}

sub append_body {
    my ($self, $appendage) = @_;
    $self->body($self->body . $appendage);
}

sub open_page { 
    my $self = shift;
    say 'open_page';
    $self->append_body('<page>');
}
sub close_page { 
    my $self = shift;
    say 'close_page';
    $self->append_body('</page>');
}

package Document::PageWithHeadersAndFooters;

use Moose;
use Perl6::Say;

extends 'Document::Page';

augment 'create' => sub {
    my $self = shift;
    $self->create_header;
    inner();
    $self->create_footer;
};

sub create_header { 
    my $self = shift;
    say 'create_header';
    $self->append_body('<header />') 
}

sub create_footer {
    my $self = shift;
    say 'create_footer';
    $self->append_body('<footer />');
}

package TPSReport;

use Moose;
use Perl6::Say;

extends 'Document::PageWithHeadersAndFooters';

augment 'create' => sub {
    say 'begin inner()';
    my $self = shift;
    $self->create_tps_report;
    say 'end inner()';
    return $self->body;
};

sub create_tps_report {
    (shift)->append_body('<report type="tps" />');
}

package main;

use Perl6::Say;
use Test::More qw(no_plan);

is(Document::Page->new->create, '<page></page>', 'Document::Page::create');
is(Document::PageWithHeadersAndFooters->new->create, '<page><header /><footer /></page>', 'Document::PageWithHeadersAndFooters::create');
is(TPSReport->new->create, '<page><header /><report type="tps" /><footer /></page>', 'TPSReport::create');

解説

Document::Page の create
sub create {
    my $self = shift;
    $self->open_page;
    inner();
    $self->close_page;
}

とありますが、inner()なんてどこにも定義していません。実はこれ Moose 側で提供しています。

is(Document::Page->new->create, '<page></page>', 'Document::Page::create');

の expect 部を見れば分かりますが、単独で呼び出した際には inner() は何もしません。

Document::PageWithHeadersAndFooters の augment => 'create'

だいぶ直感的ですね。見ればすぐ分かる。

augment 'create' => sub {
    my $self = shift;
    $self->create_header;
    inner();
    $self->create_footer;
};

Template-Toolkit で使える WRAPPER と content の関係を思い出して下さい。augment はまさに wrapper で inner() は content に当たります。(See Template::Manual::Directives # WRAPPER)

実行結果を見るとさらに明らかになりますね。

is(Document::PageWithHeadersAndFooters->new->create, '<page><header /><footer /></page>', 'Document::PageWithHeadersAndFooters::create');

つまり、

  • begin Document::PageWithHeadersAndFooters->create
    • Document::PageWithHeadersAndFooters->create_header
    • begin inner() = Document::Page->create
      • Document::Page->open_page;
      • Document::Page->close_page;
    • end inner()
    • Document::PageWithHeadersAndFooters->create_footer
  • end

みたいな呼び出しになる。

この手の奴って、今までだと明示的に hook ポイントを用意するとか、グロブ操作して無理矢理前後に処理を挟むとかってやってた訳だけど、そうした事が奇麗に書けます。むろん親クラスがinner()を用意してくれている場合に限りますがw

で、とどのつまり augment は 親が用意してくれた inner() に収まる処理を記述する 属性って言えます。

後は自明ですな。