記事

Last Modified:

Release Slack-v0.8.0 #Perl #Slack

この文書は以前のバージョンv0.7.0とv0.8.0の変更点を記述しています。

このリリースには以前のバージョンと互換性の無い変更が含まれています。

インターフェースの追加

コード節に共通の前後処理を記述できるようになりました #16

コード節において、各コード共通の前処理および後処理をそれぞれ特殊なメソッド名^および$を利用して記述できるようになりました。

action crud => qr/(.+)/ => {
    '^' => sub {
        res->stash->{item} = c->action->controller->prepare( id => req->argv->[0] );
    },
    GET => sub {
        res->stash->{item}->select;
    },
    PUT => sub {
        res->stash->{item}->update( req->body_parameters );
    },
    DELETE => sub {
        res->stash->{item}->delete;
    },
    '$' => sub {
        res->stash->{item}->execute;
    },
};

以前のバージョンではこのインターフェースが無かったため、同じ記述を繰り返す必要がありました。

action crud => qr/(.+)/ => {
    GET => sub {
        res->stash->{item} = c->action->controller->prepare( id => req->argv->[0] );
        res->stash->{item}->select;
        res->stash->{item}->execute;
    },
    PUT => sub {
        res->stash->{item} = c->action->controller->prepare( id => req->argv->[0] );
        res->stash->{item}->update( req->body_parameters );
        res->stash->{item}->execute;
    },
    DELETE => sub {
        res->stash->{item} = c->action->controller->prepare( id => req->argv->[0] );
        res->stash->{item}->delete;
        res->stash->{item}->execute;
    },
};

インターフェースの変更

各アクションの役割に関する仕様が整理されました #18

prep,action,viewの各アクションがどういう役割を担うべきか、という仕様が整理されました。

別の側面から見てみると、viewが呼び出されるときには必ずres->statusに何らかの値が入っていることになります。

prepres->statusに適当な値を設定しておいてactionをスキップし、res->statusviewで参照・処理することが出来ます。 これは例えば認証機能の実装に役立つでしょう。

prep auth => sub {
    my $self = c->action->controller;
    if ( not $self->authenticate ) {
        res->status(0);
    }
    elsif ( not $self->authorize ) {
        res->status(1);
    }
};

action admin => sub {
    res->stash->{message} = 'you are an administrator';
};

view default => sub {
    if ( res->status == 0 ) {
        res->status(HTTP_FOUND);
        res->redirect('/login');
    }
    elsif ( res->status == 1 ) {
        res->status(HTTP_FORBIDDEN);
        res->body('you do not have the permission');
    }
    else {
        res->body( res->stash->{message} );
    }
};

res->statusに数値以外を設定することもできるかもしれませんが、それを推奨するようなことはしません。

コード節にコードリファレンスを渡した場合のRequest Methodの補完が変更されました #15

コード節の実体は{ Request Method => sub { } }ですが、コードリファレンスを渡すこともできます。

コード節にコードリファレンスを渡した場合は条件を指定していないと見なされ、全てのRequest Methodにマッチします。
ただし、actionの場合に限りGETのみにマッチするよう補完されます。

prep prep => sub {    # any Request Method
    c->action->controller->authenticate or die;
};

action action => sub {    # only GET Request Method
    res->stash->{message} = 'you are authenticated';
};

# # equivalent to
# action action => {
#     GET => sub {
#         res->stash->{message} = 'you are authenticated';
#     },
# };

view view => sub {    # any Request Method
    c->app->{template}->process;
};

これは全てのRequest Methodに同じ振る舞いをさせないための措置です。

仮にactionも全てのRequest Methodにマッチすることを考えてみましょう。 その場合、GETのみに対応するアクション(これは往々にして存在します)は、action hello => { GET => sub { } }と記述する必要がありますが、怠惰な開発者によってaction hello => sub { }と記述されることでしょう。 開発者は恐らくGETだけに対応したつもりでしょうが、全てのRequest Methodで(たとえDELETEだったとしても!)同じ結果になってしまいます。

それではコードリファレンスを許容するというシンタックスシュガーを廃止すれば良いのではと思うかもしれません。
しかしaction hello => { GET => sub { } }よりaction hello => sub { }の方が格好良いと思いませんか?

以前のバージョンではprep,action,view全てにおいて、コード節にコードリファレンスを渡した場合に補完されるRequest MethodはGETでした。 prep,viewはRequest Methodが異なっていても同じ処理になる場合が多く、同じ記述を繰り返す必要がありました。

prep prep => {
    GET    => sub { c->action->controller->authenticate or die; },
    POST   => sub { c->action->controller->authenticate or die; },
    PUT    => sub { c->action->controller->authenticate or die; },
    DELETE => sub { c->action->controller->authenticate or die; },
};

action action => sub {
    res->stash->{message} = 'you are authenticated';
};

view view => {
    GET    => sub { c->app->{template}->process; },
    POST   => sub { c->app->{template}->process; },
    PUT    => sub { c->app->{template}->process; },
    DELETE => sub { c->app->{template}->process; },
};

条件節を省略した場合の補完が変更されました

前項に合わせて条件節の補完も見直されました。

条件節を省略した場合は条件を指定していないと見なされて{}が補完され、全てのリクエストにマッチします。 ただし、actionの場合に限りアクション名にマッチするよう補完されます。

これは単にCatalystのLocalを真似てaction hello => sub { }/helloにマッチする方が自然だというだけです。

prep prep     => sub { };
action action => sub { };
view view     => sub { };

# are equivalent to

prep prep => {} => sub { };
action action => { '/' => 'action' } => sub { };
view view => {} => sub { };

以前のバージョンではprep,action,view全てにおいて、条件節を省略した場合に補完されるのは{ '/' => 'アクション名' }でした。 prep,viewは全てのリクエストに対応することが多く、そのための条件節がこの変更によって省略できるようになりました。

複数の拡張子への対応が自然に記述できるようになりました

複数の拡張子(例えばindex.ja.xml.gz)に対応する条件節が自然に記述できるようになりました。

パス条件として{ '.' => 'ja' }を記述すると、.ja含まれたリクエストにマッチします。 拡張子の順序は気にする必要がありません。 index.ja.xml.gz,index.xml.ja.gz,index.xml.gz.ja全てのリクエストは{ '.' => 'ja' }にマッチします。

逆に言うとパスの途中に現れてはいけない拡張子については、それ用に処理を記述する必要があります。

例として.ja,.xml,.gzに対応するpseudo実装を示します。

action index => sub {
    res->stash->{message}   = 'hello, world';
    res->stash->{formatter} = \&sprintf;
};

view view_ja => { '.' => 'ja' } => sub {
    res->stash->{message} = maketext( res->stash->{message} );
};

view view_xml => { '.' => 'xml' } => sub {
    res->stash->{formatter} = \&XMLout;
};

view view_gz_finalize => { '.' => 'gz' } => sub {
    if ( req->env->{PATH_INFO} !~ /[.]gz\z/ ) {    # .gz shoud be last extension
        res->status(400);
        res->body(q{});
        return;
    }
    my $fh = IO::Compress::Gzip->new( \my $output );
    $fh->syswrite( res->stash->{formatter}->( res->stash->{message} ) );
    $fh->close;
    res->body($output);
};

view view_any_finalize => sub {
    res->body( res->stash->{formatter}->( res->stash->{message} ) );
};

以前のバージョンでは単一の拡張子のみを想定しており、複数の拡張子に対応するには{ '.' => qr/(?:ja|xml|gz)(?:[.].+)*/ }のようなトリッキーなパス条件とアクション内での分岐の記述が必要でした。

また、この変更によって{ '/' => 'empty', '.' => 'json' }{ PATH_INFO => qr{.*/empty.json\z} }は等価ではなくなりました。

PATH_INFOは変更されなくなりました

req->env->{PATH_INFO}は初期状態のまま一切変更されなくなりました。 その副作用として、内部実装の都合上req->env->{'/'}req->env->{'.'}が追加されています。

以前のバージョンではactionの探索をしている間のみreq->env->{PATH_INFO}が拡張子を取り除いた状態になっていました。 そのため、条件節に.を記述した場合の影響範囲が広く、また複雑に絡まっていて混乱していました。

副作用としてactionの条件節に.の指定ができるようになりました。 しかしactionはビューに関わる拡張子についてとやかく言うべきではありませんので、なるべくactionに拡張子を意識させないことを推奨します。

パス条件がマッチしたときの挙動が変更されました #19

パス条件PATH_INFO,/,.のいずれかがマッチしたが、それ以外の条件でマッチしなかった場合においては、HTTP_NOT_FOUND扱いではなくなりました。 少し分かり難いですか?具体的にどうなるかを説明します。

この変更はprep,viewに影響はありません。

actionの探索において

インターフェースの削除

ありません。

実装の変更

デバッグ出力のアクションテーブル表示における正規表現が簡略化されました

フラグは全て取り除かれ、いくつかのエスケープシーケンスや括弧も取り除かれ、実用上、概ね問題が無いと思われる状態まで簡略化されます。 そのため実際のマッチングに使用する正規表現と等価ではありません。

正確な正規表現を見たい場合はSmart::Commentsのレベルを上げてください。($ENV{Smart_Comments}####を含めるか、もしくは1を設定する) そうすれば### try:の出力行によって実際にマッチングに使用している正規表現を見ることができるでしょう。

リテラルが多過ぎるとソースフィルターが失敗するバグを修正 #13 #14

Filter::Simple::FILTER_ONLYを使用する場合、リテラルがエスケープされることがあります。 エスケープされた部分を誤って壊してしまうと致命的エラーが発生します。 これは$Filter::Simple::placeholderを置換対象から除外することによって回避されました。