CatalystでURL最適化

Catalyst::DispatchType::Chainedを使うとスッキリしたURLで組める。

似たような事はRegexやLocalRegexでも出来るけどこっちの方が重複するようなコードが軽減できる。 Chained属性に指定したPrivate Names(コントローラーのメソッド名)を使って処理をつなげる事ができる。

例えば以下のURLを処理するとき、処理はこうなる。
http://example.com/users/1/books/2

  1. /users/1は共通してユーザの検索
  2. その後に続くbooks/2はそのユーザのIDが2の本を表示

基本

では早速、以下の様なURL群を設計

  • /add
  • /*
  • /*/edit
  • /*/entries/*
  • /*/entries/*/delete


CatalystのControllerの実装はこんな風になる。

# [CAPTURE] /
sub root_cap : Chained('/') PathPart('') CaptureArgs(0) {
  my ( $self, $c ) = @_;
  $c->log->debug("[CAPTURE]root_cap");
}

# [CAPTURE] /*
sub index_cap : Chained('root_cap') PathPart('') CaptureArgs(1) {
    my ( $self, $c, $member_id ) = @_;
    $c->log->debug("[CAPTURE]index_cap $member_id");
}

# [CAPTURE] /*/entries/*
sub entries_index_cap
  : Chained('index_cap') PathPart('entries') CaptureArgs(1) {
    my ( $self, $c, $entry_id ) = @_;
    $c->log->debug("[CAPTURE]entries_index_cap $entry_id");
}

# /add
sub add : Chained('root_cap') PathPart('add') Args(0) {
    my ( $self, $c ) = @_;
    $c->log->debug("add");
}

# /*
sub show : Chained('index_cap') PathPart('') Args(0) {
    my ( $self, $c ) = @_;
    $c->log->debug("index");
}

# /*/edit
sub edit : Chained('index_cap') PathPart('edit') Args(0) {
    my ( $self, $c, $alias_id ) = @_;
    $c->log->debug("edit");
}

# /*/entries/*
sub entries_index : Chained('entries_index_cap') PathPart('') Args(0) {
    my ( $self, $c ) = @_;
    $c->log->debug("entries_index");
}

# /*/entries/*/delete
sub entries_delete : Chained('entries_index_cap') PathPart('delete') Args(0) {
    my ( $self, $c ) = @_;
    $c->log->debug("entries_delete");
}


これを実装してCatalystを起動すると、以下のようなログがでる。

[debug] Loaded Chained actions:
.-------------------------------------+--------------------------------------.
| Path Spec                           | Private                              |
+-------------------------------------+--------------------------------------+
| /add                                | /members/root_cap (0)                |
|                                     | => /members/add                      |
| /*/edit                             | /members/root_cap (0)                |
|                                     | -> /members/index_cap (1)            |
|                                     | => /members/edit                     |
| /*/entries/*/delete                 | /members/root_cap (0)                |
|                                     | -> /members/index_cap (1)            |
|                                     | -> /members/entries_index_cap (1)    |
|                                     | => /members/entries_delete           |
| /*/entries/*                        | /members/root_cap (0)                |
|                                     | -> /members/index_cap (1)            |
|                                     | -> /members/entries_index_cap (1)    |
|                                     | => /members/entries_index            |
| /*                                  | /members/root_cap (0)                |
|                                     | -> /members/index_cap (1)            |
|                                     | => /members/index                    |
'-------------------------------------+--------------------------------------'

試しに以下のURLにアクセスすると、連続的にchainedしたPrivate Namesが実行されるのが確認できる。

[debug] [CAPTURE]root_cap
[debug] [CAPTURE]index_cap 1
[debug] [CAPTURE]entries_index_cap 2
[debug] entries_delete
.----------------------------------------------------------------+-----------.
| Action                                                         | Time      |
+----------------------------------------------------------------+-----------+
| /auto                                                          | 0.000981s |
| /members/root_cap                                              | 0.000323s |
| /members/index_cap                                             | 0.000166s |
| /members/entries_index_cap                                     | 0.000211s |
| /members/entries_delete                                        | 0.000135s |
| /end                                                           | 0.010976s |
|  -> Example::View::TT->process                                 | 0.005886s |
'----------------------------------------------------------------+-----------'

属性の説明

Chained
Private Nameを指定することで前処理を指定、あくまでPrivate Nameなので/another/methodのように別のパッケージのPrivate Nameも指定できる。
PathPart
当てはめるパス
CaptureArgs
指定するとキャプチャして、パス後方の指定された数を引数として取得
Args
実際のパスにマッチしたときの処理、パス後方の指定された数を引数として取得

大きく分けて2種類

Chainedを利用したURLの指定には大きく分けて2種類。属性CaptureArgsとArgsによって分けられる。

CaptureArgs
を指定するとキャプチャして処理
Args
は実際のURLにマッチしたときの処理


例えば、/addを実装してみると以下のコードになる。このとき/を指定してもroot_capは実行されない。

# [CAPTURE] /
sub root_cap : Chained('/') PathPart('') CaptureArgs(0) {
  my ( $self, $c ) = @_;
  $c->log->debug("[CAPTURE]root_cap");
}

# /add
sub add : Chained('root_cap') PathPart('add') Args(0) {
    my ( $self, $c ) = @_;
    $c->log->debug("add");
}

引数は付ける方が良い

属性のCaptureArgsとArgsは引数を指定した方が良い、指定しないと前方一致のような動きをしていまう。

試しに、Argsの引数をなくすと

# /*/entries/*/delete
sub entries_delete : Chained('entries_index_cap') PathPart('delete') Args() {
    my ( $self, $c ) = @_;
    $c->log->debug("entries_delete");
}

起動時のPath Specの後方に「...」が付いてしまう。

.-------------------------------------+--------------------------------------.
| Path Spec                           | Private                              |
+-------------------------------------+--------------------------------------+
| /*/entries/*/delete/...             | /members/root_cap (0)                |
|                                     | -> /members/index_cap (1)            |
|                                     | -> /members/entries_index_cap (1)    |
|                                     | => /members/entries_delete           |

後方が何でも一致する意味も無いと思うので、なるべく引数は指定した方がよさげ。