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
の各アクションがどういう役割を担うべきか、という仕様が整理されました。
prep
は事前処理を行なうために存在するよう定められました。- 無条件で次の
prep
の探索が継続され、複数個のprep
が実行されます。
- 無条件で次の
action
はres->status
を定義するために存在するよう定められました。action
の探索開始より前に(つまりprep
で)既にres->status
が定義されていると、action
の探索は行なわれません。res->status
が定義されない間、次のaction
の探索が継続されますが、パス条件節(PATH_INFO
,/
,.
)のいずれかがマッチすると必ずres->status
は何らかの値で定義されます。つまりパス条件節のいずれかがマッチするとaction
の探索は終了することになります。
view
はres->body
を定義するために存在するよう定められました。view
の探索開始より前に(つまりprep
またはaction
で)既にres->body
が定義されていると、view
の探索は行なわれません。res->body
が定義されない間、次のview
の探索が継続され、複数個のview
が実行されます。
別の側面から見てみると、view
が呼び出されるときには必ずres->status
に何らかの値が入っていることになります。
prep
でres->status
に適当な値を設定しておいてaction
をスキップし、res->status
をview
で参照・処理することが出来ます。
これは例えば認証機能の実装に役立つでしょう。
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} ) );
};
view view_any_finalize
はview view_gz_finalize
より後方に記述する必要があることに注意してください。- このpseudo実装は
/index( (\.ja)?(\.xml)?(\.gz)? | (.xml)?(.ja)?(.gz)? )\z/
なリクエストに対して正常なレスポンスを返します。 .gz
が途中に含まれるのを拒否するために、PATH_INFO
をチェックしています。 内部実装の都合上、req->env->{'.'} = '.ja.xml.gz'
になっていますが、これを頼るべきではありません。将来的に変更される可能性があります。- 例えば
index.xml.xml
のように同一拡張子が複数個含まれていても、対応するアクションview view_xml
はただ1度だけ呼び出されます。
以前のバージョンでは単一の拡張子のみを想定しており、複数の拡張子に対応するには{ '.' => 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
の探索において
- いずれかのパス条件がマッチしなかった場合、次の
action
の探索が継続されます。 - 全てのパス条件がマッチした場合
- パス条件以外の条件のいずれかにマッチしなかったら
HTTP_METHOD_NOT_ALLOWED
もしくはHTTP_BAD_REQUEST
が設定されます。
以前のバージョンでは、次のaction
の探索を継続していました。 - パス条件以外の条件に全てマッチしたらアクションコードが呼び出され、(
res->status
が未定義であれば)HTTP_OK
が設定されます。
- パス条件以外の条件のいずれかにマッチしなかったら
インターフェースの削除
ありません。
実装の変更
デバッグ出力のアクションテーブル表示における正規表現が簡略化されました
フラグは全て取り除かれ、いくつかのエスケープシーケンスや括弧も取り除かれ、実用上、概ね問題が無いと思われる状態まで簡略化されます。 そのため実際のマッチングに使用する正規表現と等価ではありません。
正確な正規表現を見たい場合はSmart::Comments
のレベルを上げてください。($ENV{Smart_Comments}
に####
を含めるか、もしくは1
を設定する)
そうすれば### try:
の出力行によって実際にマッチングに使用している正規表現を見ることができるでしょう。
リテラルが多過ぎるとソースフィルターが失敗するバグを修正 #13 #14
Filter::Simple::FILTER_ONLY
を使用する場合、リテラルがエスケープされることがあります。
エスケープされた部分を誤って壊してしまうと致命的エラーが発生します。
これは$Filter::Simple::placeholder
を置換対象から除外することによって回避されました。