File Coverage

blib/lib/Catalyst/DispatchType/Chained.pm
Criterion Covered Total %
statement 207 222 93.2
branch 94 120 78.3
condition 43 56 76.7
subroutine 17 17 100.0
pod 6 6 100.0
total 367 421 87.1


line stmt bran cond sub pod time code
1             package Catalyst::DispatchType::Chained;
2              
3 103     103   98898 use Moose;
  103         310  
  103         1094  
4             extends 'Catalyst::DispatchType';
5              
6 103     103   811105 use Text::SimpleTable;
  103         337  
  103         3504  
7 103     103   52642 use Catalyst::ActionChain;
  103         493  
  103         4659  
8 103     103   1025 use Catalyst::Utils;
  103         300  
  103         2833  
9 103     103   770 use URI;
  103         279  
  103         2242  
10 103     103   785 use Scalar::Util ();
  103         276  
  103         2887  
11 103     103   714 use Encode 2.21 'decode_utf8';
  103         3817  
  103         17897  
12              
13             has _endpoints => (
14             is => 'rw',
15             isa => 'ArrayRef',
16             required => 1,
17             default => sub{ [] },
18             );
19              
20             has _actions => (
21             is => 'rw',
22             isa => 'HashRef',
23             required => 1,
24             default => sub{ {} },
25             );
26              
27             has _children_of => (
28             is => 'rw',
29             isa => 'HashRef',
30             required => 1,
31             default => sub{ {} },
32             );
33              
34 103     103   977 no Moose;
  103         373  
  103         856  
35              
36             # please don't perltidy this. hairy code within.
37              
38             =head1 NAME
39              
40             Catalyst::DispatchType::Chained - Path Part DispatchType
41              
42             =head1 SYNOPSIS
43              
44             Path part matching, allowing several actions to sequentially take care of processing a request:
45              
46             # root action - captures one argument after it
47             sub foo_setup : Chained('/') PathPart('foo') CaptureArgs(1) {
48             my ( $self, $c, $foo_arg ) = @_;
49             ...
50             }
51              
52             # child action endpoint - takes one argument
53             sub bar : Chained('foo_setup') Args(1) {
54             my ( $self, $c, $bar_arg ) = @_;
55             ...
56             }
57              
58             =head1 DESCRIPTION
59              
60             Dispatch type managing default behaviour. For more information on
61             dispatch types, see:
62              
63             =over 4
64              
65             =item * L<Catalyst::Manual::Intro> for how they affect application authors
66              
67             =item * L<Catalyst::DispatchType> for implementation information.
68              
69             =back
70              
71             =head1 METHODS
72              
73             =head2 $self->list($c)
74              
75             Debug output for Path Part dispatch points
76              
77             =cut
78              
79             sub list {
80 2     2 1 8 my ( $self, $c ) = @_;
81              
82 2 50       114 return unless $self->_endpoints;
83              
84 2         16 my $avail_width = Catalyst::Utils::term_width() - 9;
85 2 50       21 my $col1_width = ($avail_width * .50) < 35 ? 35 : int($avail_width * .50);
86 2         9 my $col2_width = $avail_width - $col1_width;
87 2         28 my $paths = Text::SimpleTable->new(
88             [ $col1_width, 'Path Spec' ], [ $col2_width, 'Private' ],
89             );
90              
91 2         217 my $has_unattached_actions;
92 2         14 my $unattached_actions = Text::SimpleTable->new(
93             [ $col1_width, 'Private' ], [ $col2_width, 'Missing parent' ],
94             );
95              
96 2         165 ENDPOINT: foreach my $endpoint (
97 4         111 sort { $a->reverse cmp $b->reverse }
98 2         74 @{ $self->_endpoints }
99             ) {
100 5         18698 my $args = $endpoint->list_extra_info->{Args};
101              
102 5         15 my @parts;
103 5 100       202 if($endpoint->has_args_constraints) {
    50          
104 2         87 @parts = map { "{$_}" } $endpoint->all_args_constraints;
  2         13  
105             } elsif(defined $endpoint->attributes->{Args}) {
106 3 50       83 @parts = (defined($endpoint->attributes->{Args}[0]) ? (("*") x $args) : '...');
107             }
108              
109 5         108 my @parents = ();
110 5         14 my $parent = "DUMMY";
111 5         18 my $extra = $self->_list_extra_http_methods($endpoint);
112 5         23 my $consumes = $self->_list_extra_consumes($endpoint);
113 5         21 my $scheme = $self->_list_extra_scheme($endpoint);
114 5         11 my $curr = $endpoint;
115 5         20 while ($curr) {
116 11 100       42 if (my $cap = $curr->list_extra_info->{CaptureArgs}) {
117 1 50       40 if($curr->has_captures_constraints) {
118 1         40 my $names = join '/', map { "{$_}" } $curr->all_captures_constraints;
  1         6  
119 1         40 unshift(@parts, $names);
120             } else {
121 0         0 unshift(@parts, (("*") x $cap));
122             }
123             }
124 11 50       312 if (my $pp = $curr->attributes->{PathPart}) {
125 11 100 66     69 unshift(@parts, $pp->[0])
126             if (defined $pp->[0] && length $pp->[0]);
127             }
128 11         357 $parent = $curr->attributes->{Chained}->[0];
129 11         334 $curr = $self->_actions->{$parent};
130 11 100       39 unshift(@parents, $curr) if $curr;
131             }
132 5 50       26 if ($parent ne '/') {
133 0         0 $has_unattached_actions = 1;
134 0   0     0 $unattached_actions->row('/' . ($parents[0] || $endpoint)->reverse, $parent);
135 0         0 next ENDPOINT;
136             }
137 5         12 my @rows;
138 5         14 foreach my $p (@parents) {
139 6         22 my $name = "/${p}";
140              
141 6 50       18 if (defined(my $extra = $self->_list_extra_http_methods($p))) {
142 0         0 $name = "${extra} ${name}";
143             }
144 6 50       23 if (defined(my $cap = $p->list_extra_info->{CaptureArgs})) {
145 6 100       263 if($p->has_captures_constraints) {
146 1         3 my $tc = join ',', @{$p->captures_constraints};
  1         29  
147 1         38 $name .= " ($tc)";
148             } else {
149 5         17 $name .= " ($cap)";
150             }
151             }
152 6 50       24 if (defined(my $ct = $p->list_extra_info->{Consumes})) {
153 0         0 $name .= ' :'.$ct;
154             }
155 6 50       22 if (defined(my $s = $p->list_extra_info->{Scheme})) {
156 0         0 $scheme = uc $s;
157             }
158              
159 6 100       24 unless ($p eq $parents[0]) {
160 1         3 $name = "-> ${name}";
161             }
162 6         24 push(@rows, [ '', $name ]);
163             }
164              
165 5         13 my $endpoint_arg_info = $endpoint;
166 5 100       199 if($endpoint->has_args_constraints) {
167 2         11 my $tc = join ',', @{$endpoint->args_constraints};
  2         53  
168 2         76 $endpoint_arg_info .= " ($tc)";
169             } else {
170 3 50       99 $endpoint_arg_info .= defined($endpoint->attributes->{Args}[0]) ? " ($args)" : " (...)";
171             }
172 5 50       46 push(@rows, [ '', (@rows ? "=> " : '').($extra ? "$extra " : ''). ($scheme ? "$scheme: ":'')."/${endpoint_arg_info}". ($consumes ? " :$consumes":"" ) ]);
    50          
    50          
    50          
173 5         14 my @display_parts = map { $_ =~s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg; decode_utf8 $_ } @parts;
  16         147  
  0         0  
  16         124  
174 5   50     50 $rows[0][0] = join('/', '', @display_parts) || '/';
175 5         29 $paths->row(@$_) for @rows;
176             }
177              
178 2         408 $c->log->debug( "Loaded Chained actions:\n" . $paths->draw . "\n" );
179 2 50       452 $c->log->debug( "Unattached Chained actions:\n", $unattached_actions->draw . "\n" )
180             if $has_unattached_actions;
181             }
182              
183             sub _list_extra_http_methods {
184 11     11   27 my ( $self, $action ) = @_;
185 11 50       28 return unless defined $action->list_extra_info->{HTTP_METHODS};
186 0         0 return join(', ', @{$action->list_extra_info->{HTTP_METHODS}});
  0         0  
187              
188             }
189              
190             sub _list_extra_consumes {
191 5     5   12 my ( $self, $action ) = @_;
192 5 50       12 return unless defined $action->list_extra_info->{CONSUMES};
193 0         0 return join(', ', @{$action->list_extra_info->{CONSUMES}});
  0         0  
194             }
195              
196             sub _list_extra_scheme {
197 5     5   13 my ( $self, $action ) = @_;
198 5 50       13 return unless defined $action->list_extra_info->{Scheme};
199 0         0 return uc $action->list_extra_info->{Scheme};
200             }
201              
202             =head2 $self->match( $c, $path )
203              
204             Calls C<recurse_match> to see if a chain matches the C<$path>.
205              
206             =cut
207              
208             sub match {
209 762     762 1 2073 my ( $self, $c, $path ) = @_;
210              
211 762         19085 my $request = $c->request;
212 762 100       1840 return 0 if @{$request->args};
  762         2752  
213              
214 562         2593 my @parts = split('/', $path);
215              
216 562         2603 my ($chain, $captures, $parts) = $self->recurse_match($c, '/', \@parts);
217              
218 562 100 100     2524 if ($parts && @$parts) {
219 80         280 for my $arg (@$parts) {
220 100         297 $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
  24         111  
221 100         183 push @{$request->args}, $arg;
  100         323  
222             }
223             }
224              
225 562 100       2589 return 0 unless $chain;
226              
227 245         2289 my $action = Catalyst::ActionChain->from_chain($chain);
228              
229 245         1862 $request->action("/${action}");
230 245         1173 $request->match("/${action}");
231 245         7239 $request->captures($captures);
232 245         6377 $c->action($action);
233 245         7302 $c->namespace( $action->namespace );
234              
235 245         1843 return 1;
236             }
237              
238             =head2 $self->recurse_match( $c, $parent, \@path_parts )
239              
240             Recursive search for a matching chain.
241              
242             =cut
243              
244             sub recurse_match {
245 1216     1216 1 3458 my ( $self, $c, $parent, $path_parts ) = @_;
246 1216         39648 my $children = $self->_children_of->{$parent};
247 1216 50       3187 return () unless $children;
248 1216         2608 my $best_action;
249             my @captures;
250 1216         11216 TRY: foreach my $try_part (sort { length($b) <=> length($a) }
  80372         110036  
251             keys %$children) {
252             # $b then $a to try longest part first
253 19729         41487 my @parts = @$path_parts;
254 19729 100       35598 if (length $try_part) { # test and strip PathPart
255             next TRY unless
256             ($try_part eq join('/', # assemble equal number of parts
257             splice( # and strip them off @parts as well
258 19108 100       26936 @parts, 0, scalar(@{[split('/', $try_part)]})
  19108         72055  
259             ))); # @{[]} to avoid split to @_
260             }
261 1207         2376 my @try_actions = @{$children->{$try_part}};
  1207         4706  
262 1207         2648 TRY_ACTION: foreach my $action (@try_actions) {
263              
264              
265 1583 100       47793 if (my $capture_attr = $action->attributes->{CaptureArgs}) {
266 695   100     19931 my $capture_count = $action->number_of_captures|| 0;
267              
268             # Short-circuit if not enough remaining parts
269 695 100       2089 next TRY_ACTION unless @parts >= $capture_count;
270              
271 676         1103 my @captures;
272 676         1669 my @parts = @parts; # localise
273              
274             # strip CaptureArgs into list
275 676         1548 push(@captures, splice(@parts, 0, $capture_count));
276              
277             # check if the action may fit, depending on a given test by the app
278 676 100       2568 next TRY_ACTION unless $action->match_captures($c, \@captures);
279              
280             # try the remaining parts against children of this action
281 654         19007 my ($actions, $captures, $action_parts, $n_pathparts) = $self->recurse_match(
282             $c, '/'.$action->reverse, \@parts
283             );
284             # No best action currently
285             # OR The action has less parts
286             # OR The action has equal parts but less captured data (ergo more defined)
287 654 100 100     3425 if ($actions &&
      100        
288             (!$best_action ||
289             $#$action_parts < $#{$best_action->{parts}} ||
290             ($#$action_parts == $#{$best_action->{parts}} &&
291             $#$captures < $#{$best_action->{captures}} &&
292             $n_pathparts > $best_action->{n_pathparts}))) {
293 301         8737 my @pathparts = split /\//, $action->attributes->{PathPart}->[0];
294 301         4048 $best_action = {
295             actions => [ $action, @$actions ],
296             captures=> [ @captures, @$captures ],
297             parts => $action_parts,
298             n_pathparts => scalar(@pathparts) + $n_pathparts,
299             };
300             }
301             }
302             else {
303             {
304 888         2042 local $c->req->{arguments} = [ @{$c->req->args}, @parts ];
  888         1609  
  888         3083  
305 888 100       3953 next TRY_ACTION unless $action->match($c);
306             }
307 294         8368 my $args_attr = $action->attributes->{Args}->[0];
308 294         1087 my $args_count = $action->comparable_arg_number;
309 294         8041 my @pathparts = split /\//, $action->attributes->{PathPart}->[0];
310             # No best action currently
311             # OR This one matches with fewer parts left than the current best action,
312             # And therefore is a better match
313             # OR No parts and this expects 0
314             # The current best action might also be Args(0),
315             # but we couldn't chose between then anyway so we'll take the last seen
316 294 100 66     3235 if (
      66        
      66        
      66        
      100        
      100        
317             !$best_action ||
318 34         416 @parts < @{$best_action->{parts}} ||
319             (
320             !@parts &&
321             defined($args_attr) &&
322             (
323             $args_count eq "0" &&
324             (
325             ($c->config->{use_chained_args_0_special_case}||0) ||
326             (
327             exists($best_action->{args_count}) && defined($best_action->{args_count}) ?
328             ($best_action->{args_count} ne 0) : 1
329             )
330             )
331             )
332             )
333             ){
334 262         2390 $best_action = {
335             actions => [ $action ],
336             captures=> [],
337             parts => \@parts,
338             args_count => $args_count,
339             n_pathparts => scalar(@pathparts),
340             };
341             }
342             }
343             }
344             }
345 1216 100       6999 return @$best_action{qw/actions captures parts n_pathparts/} if $best_action;
346 663         2144 return ();
347             }
348              
349             =head2 $self->register( $c, $action )
350              
351             Calls register_path for every Path attribute for the given $action.
352              
353             =cut
354              
355             sub register {
356 61271     61271 1 139531 my ( $self, $c, $action ) = @_;
357              
358 61271 100       100254 my @chained_attr = @{ $action->attributes->{Chained} || [] };
  61271         1634810  
359              
360 61271 100       248670 return 0 unless @chained_attr;
361              
362 12309 100       29466 if (@chained_attr > 1) {
363 1         4 Catalyst::Exception->throw(
364             "Multiple Chained attributes not supported registering ${action}"
365             );
366             }
367 12308         23080 my $chained_to = $chained_attr[0];
368              
369 12308 100       50658 Catalyst::Exception->throw(
370             "Actions cannot chain to themselves registering /${action}"
371             ) if ($chained_to eq '/' . $action);
372              
373 12306   100     404457 my $children = ($self->_children_of->{ $chained_to } ||= {});
374              
375 12306 100       22426 my @path_part = @{ $action->attributes->{PathPart} || [] };
  12306         320388  
376              
377 12306         316348 my $part = $action->name;
378              
379 12306 100 100     57761 if (@path_part == 1 && defined $path_part[0]) {
    50          
380 10235         20851 $part = $path_part[0];
381             } elsif (@path_part > 1) {
382 0         0 Catalyst::Exception->throw(
383             "Multiple PathPart attributes not supported registering " . $action->reverse()
384             );
385             }
386              
387 12306 100       29705 if ($part =~ m(^/)) {
388 1         30 Catalyst::Exception->throw(
389             "Absolute parameters to PathPart not allowed registering " . $action->reverse()
390             );
391             }
392              
393 12305         48120 my $encoded_part = URI->new($part)->canonical;
394 12305         762466 $encoded_part =~ s{(?<=[^/])/+\z}{};
395              
396 12305         416403 $action->attributes->{PathPart} = [ $encoded_part ];
397              
398 12305   100     22752 unshift(@{ $children->{$encoded_part} ||= [] }, $action);
  12305         35085  
399              
400 12305         482343 $self->_actions->{'/'.$action->reverse} = $action;
401              
402 12305 50 66     320528 if (exists $action->attributes->{Args} and exists $action->attributes->{CaptureArgs}) {
403 0         0 Catalyst::Exception->throw(
404             "Combining Args and CaptureArgs attributes not supported registering " .
405             $action->reverse()
406             );
407             }
408              
409 12305 100       314647 unless ($action->attributes->{CaptureArgs}) {
410 7106         13219 unshift(@{ $self->_endpoints }, $action);
  7106         219686  
411             }
412              
413 12305         59790 return 1;
414             }
415              
416             =head2 $self->uri_for_action($action, $captures)
417              
418             Get the URI part for the action, using C<$captures> to fill
419             the capturing parts.
420              
421             =cut
422              
423             sub uri_for_action {
424 55     55 1 144 my ( $self, $action, $captures ) = @_;
425              
426             return undef unless ($action->attributes->{Chained}
427 55 100 66     1562 && !$action->attributes->{CaptureArgs});
428              
429 51         145 my @parts = ();
430 51         134 my @captures = @$captures;
431 51         109 my $parent = "DUMMY";
432 51         94 my $curr = $action;
433             # If this is an action chain get the last action in the chain
434 51 100       306 if($curr->can('chain') ) {
435 8         19 $curr = ${$curr->chain}[-1];
  8         222  
436             }
437 51         200 while ($curr) {
438 124 100       3965 if (my $cap = $curr->number_of_captures) {
439 57 100       222 return undef unless @captures >= $cap; # not enough captures
440 55 50       146 if ($cap) {
441 55         173 unshift(@parts, splice(@captures, -$cap));
442             }
443             }
444 122 50       3296 if (my $pp = $curr->attributes->{PathPart}) {
445 122 100 66     627 unshift(@parts, $pp->[0])
446             if (defined($pp->[0]) && length($pp->[0]));
447             }
448 122         3933 $parent = $curr->attributes->{Chained}->[0];
449 122         3739 $curr = $self->_actions->{$parent};
450             }
451              
452 49 50       164 return undef unless $parent eq '/'; # fail for dangling action
453              
454 49 100       135 return undef if @captures; # fail for too many captures
455              
456 47         182 return join('/', '', @parts);
457              
458             }
459              
460             =head2 $c->expand_action($action)
461              
462             Return a list of actions that represents a chained action. See
463             L<Catalyst::Dispatcher> for more info. You probably want to
464             use the expand_action it provides rather than this directly.
465              
466             =cut
467              
468             sub expand_action {
469 168     168 1 409 my ($self, $action) = @_;
470              
471 168 100 66     4683 return unless $action->attributes && $action->attributes->{Chained};
472              
473 97         222 my @chain;
474 97         175 my $curr = $action;
475              
476 97         315 while ($curr) {
477 240         590 push @chain, $curr;
478 240         6300 my $parent = $curr->attributes->{Chained}->[0];
479 240         7204 $curr = $self->_actions->{$parent};
480             }
481              
482 97         519 return Catalyst::ActionChain->from_chain([reverse @chain]);
483             }
484              
485             __PACKAGE__->meta->make_immutable;
486             1;
487              
488             =head1 USAGE
489              
490             =head2 Introduction
491              
492             The C<Chained> attribute allows you to chain public path parts together
493             by their private names. A chain part's path can be specified with
494             C<PathPart> and can be declared to expect an arbitrary number of
495             arguments. The endpoint of the chain specifies how many arguments it
496             gets through the C<Args> attribute. C<:Args(0)> would be none at all,
497             C<:Args> without an integer would be unlimited. The path parts that
498             aren't endpoints are using C<CaptureArgs> to specify how many parameters
499             they expect to receive. As an example setup:
500              
501             package MyApp::Controller::Greeting;
502             use base qw/ Catalyst::Controller /;
503              
504             # this is the beginning of our chain
505             sub hello : PathPart('hello') Chained('/') CaptureArgs(1) {
506             my ( $self, $c, $integer ) = @_;
507             $c->stash->{ message } = "Hello ";
508             $c->stash->{ arg_sum } = $integer;
509             }
510              
511             # this is our endpoint, because it has no :CaptureArgs
512             sub world : PathPart('world') Chained('hello') Args(1) {
513             my ( $self, $c, $integer ) = @_;
514             $c->stash->{ message } .= "World!";
515             $c->stash->{ arg_sum } += $integer;
516              
517             $c->response->body( join "<br/>\n" =>
518             $c->stash->{ message }, $c->stash->{ arg_sum } );
519             }
520              
521             The debug output provides a separate table for chained actions, showing
522             the whole chain as it would match and the actions it contains. Here's an
523             example of the startup output with our actions above:
524              
525             ...
526             [debug] Loaded Path Part actions:
527             .-----------------------+------------------------------.
528             | Path Spec | Private |
529             +-----------------------+------------------------------+
530             | /hello/*/world/* | /greeting/hello (1) |
531             | | => /greeting/world |
532             '-----------------------+------------------------------'
533             ...
534              
535             As you can see, Catalyst only deals with chains as whole paths and
536             builds one for each endpoint, which are the actions with C<:Chained> but
537             without C<:CaptureArgs>.
538              
539             Let's assume this application gets a request at the path
540             C</hello/23/world/12>. What happens then? First, Catalyst will dispatch
541             to the C<hello> action and pass the value C<23> as an argument to it
542             after the context. It does so because we have previously used
543             C<:CaptureArgs(1)> to declare that it has one path part after itself as
544             its argument. We told Catalyst that this is the beginning of the chain
545             by specifying C<:Chained('/')>. Also note that instead of saying
546             C<:PathPart('hello')> we could also just have said C<:PathPart>, as it
547             defaults to the name of the action.
548              
549             After C<hello> has run, Catalyst goes on to dispatch to the C<world>
550             action. This is the last action to be called: Catalyst knows this is an
551             endpoint because we did not specify a C<:CaptureArgs>
552             attribute. Nevertheless we specify that this action expects an argument,
553             but at this point we're using C<:Args(1)> to do that. We could also have
554             said C<:Args> or left it out altogether, which would mean this action
555             would get all arguments that are there. This action's C<:Chained>
556             attribute says C<hello> and tells Catalyst that the C<hello> action in
557             the current controller is its parent.
558              
559             With this we have built a chain consisting of two public path parts.
560             C<hello> captures one part of the path as its argument, and also
561             specifies the path root as its parent. So this part is
562             C</hello/$arg>. The next part is the endpoint C<world>, expecting one
563             argument. It sums up to the path part C<world/$arg>. This leads to a
564             complete chain of C</hello/$arg/world/$arg> which is matched against the
565             requested paths.
566              
567             This example application would, if run and called by e.g.
568             C</hello/23/world/12>, set the stash value C<message> to "Hello" and the
569             value C<arg_sum> to "23". The C<world> action would then append "World!"
570             to C<message> and add C<12> to the stash's C<arg_sum> value. For the
571             sake of simplicity no view is shown. Instead we just put the values of
572             the stash into our body. So the output would look like:
573              
574             Hello World!
575             35
576              
577             And our test server would have given us this debugging output for the
578             request:
579              
580             ...
581             [debug] "GET" request for "hello/23/world/12" from "127.0.0.1"
582             [debug] Path is "/greeting/world"
583             [debug] Arguments are "12"
584             [info] Request took 0.164113s (6.093/s)
585             .------------------------------------------+-----------.
586             | Action | Time |
587             +------------------------------------------+-----------+
588             | /greeting/hello | 0.000029s |
589             | /greeting/world | 0.000024s |
590             '------------------------------------------+-----------'
591             ...
592              
593             What would be common uses of this dispatch technique? It gives the
594             possibility to split up logic that contains steps that each depend on
595             each other. An example would be, for example, a wiki path like
596             C</wiki/FooBarPage/rev/23/view>. This chain can be easily built with
597             these actions:
598              
599             sub wiki : PathPart('wiki') Chained('/') CaptureArgs(1) {
600             my ( $self, $c, $page_name ) = @_;
601             # load the page named $page_name and put the object
602             # into the stash
603             }
604              
605             sub rev : PathPart('rev') Chained('wiki') CaptureArgs(1) {
606             my ( $self, $c, $revision_id ) = @_;
607             # use the page object in the stash to get at its
608             # revision with number $revision_id
609             }
610              
611             sub view : PathPart Chained('rev') Args(0) {
612             my ( $self, $c ) = @_;
613             # display the revision in our stash. Another option
614             # would be to forward a compatible object to the action
615             # that displays the default wiki pages, unless we want
616             # a different interface here, for example restore
617             # functionality.
618             }
619              
620             It would now be possible to add other endpoints, for example C<restore>
621             to restore this specific revision as the current state.
622              
623             You don't have to put all the chained actions in one controller. The
624             specification of the parent through C<:Chained> also takes an absolute
625             action path as its argument. Just specify it with a leading C</>.
626              
627             If you want, for example, to have actions for the public paths
628             C</foo/12/edit> and C</foo/12>, just specify two actions with
629             C<:PathPart('foo')> and C<:Chained('/')>. The handler for the former
630             path needs a C<:CaptureArgs(1)> attribute and a endpoint with
631             C<:PathPart('edit')> and C<:Chained('foo')>. For the latter path give
632             the action just a C<:Args(1)> to mark it as endpoint. This sums up to
633             this debugging output:
634              
635             ...
636             [debug] Loaded Path Part actions:
637             .-----------------------+------------------------------.
638             | Path Spec | Private |
639             +-----------------------+------------------------------+
640             | /foo/* | /controller/foo_view |
641             | /foo/*/edit | /controller/foo_load (1) |
642             | | => /controller/edit |
643             '-----------------------+------------------------------'
644             ...
645              
646             Here's a more detailed specification of the attributes belonging to
647             C<:Chained>:
648              
649             =head2 Attributes
650              
651             =over 8
652              
653             =item PathPart
654              
655             Sets the name of this part of the chain. If it is specified without
656             arguments, it takes the name of the action as default. So basically
657             C<sub foo :PathPart> and C<sub foo :PathPart('foo')> are identical.
658             This can also contain slashes to bind to a deeper level. An action
659             with C<sub bar :PathPart('foo/bar') :Chained('/')> would bind to
660             C</foo/bar/...>. If you don't specify C<:PathPart> it has the same
661             effect as using C<:PathPart>, it would default to the action name.
662              
663             =item PathPrefix
664              
665             Sets PathPart to the path_prefix of the current controller.
666              
667             =item Chained
668              
669             Has to be specified for every child in the chain. Possible values are
670             absolute and relative private action paths or a single slash C</> to
671             tell Catalyst that this is the root of a chain. The attribute
672             C<:Chained> without arguments also defaults to the C</> behavior.
673             Relative action paths may use C<../> to refer to actions in parent
674             controllers.
675              
676             Because you can specify an absolute path to the parent action, it
677             doesn't matter to Catalyst where that parent is located. So, if your
678             design requests it, you can redispatch a chain through any controller or
679             namespace you want.
680              
681             Another interesting possibility gives C<:Chained('.')>, which chains
682             itself to an action with the path of the current controller's namespace.
683             For example:
684              
685             # in MyApp::Controller::Foo
686             sub bar : Chained CaptureArgs(1) { ... }
687              
688             # in MyApp::Controller::Foo::Bar
689             sub baz : Chained('.') Args(1) { ... }
690              
691             This builds up a chain like C</bar/*/baz/*>. The specification of C<.>
692             as the argument to Chained here chains the C<baz> action to an action
693             with the path of the current controller namespace, namely
694             C</foo/bar>. That action chains directly to C</>, so the C</bar/*/baz/*>
695             chain comes out as the end product.
696              
697             =item ChainedParent
698              
699             Chains an action to another action with the same name in the parent
700             controller. For Example:
701              
702             # in MyApp::Controller::Foo
703             sub bar : Chained CaptureArgs(1) { ... }
704              
705             # in MyApp::Controller::Foo::Bar
706             sub bar : ChainedParent Args(1) { ... }
707              
708             This builds a chain like C</bar/*/bar/*>.
709              
710             =item CaptureArgs
711              
712             Must be specified for every part of the chain that is not an
713             endpoint. With this attribute Catalyst knows how many of the following
714             parts of the path (separated by C</>) this action wants to capture as
715             its arguments. If it doesn't expect any, just specify
716             C<:CaptureArgs(0)>. The captures get passed to the action's C<@_> right
717             after the context, but you can also find them as array references in
718             C<< $c->request->captures->[$level] >>. The C<$level> is the
719             level of the action in the chain that captured the parts of the path.
720              
721             An action that is part of a chain (that is, one that has a C<:Chained>
722             attribute) but has no C<:CaptureArgs> attribute is treated by Catalyst
723             as a chain end.
724              
725             Allowed values for CaptureArgs is a single integer (CaptureArgs(2), meaning two
726             allowed) or you can declare a L<Moose>, L<MooseX::Types> or L<Type::Tiny>
727             named constraint such as CaptureArgs(Int,Str) would require two args with
728             the first being a Integer and the second a string. You may declare your own
729             custom type constraints and import them into the controller namespace:
730              
731             package MyApp::Controller::Root;
732              
733             use Moose;
734             use MooseX::MethodAttributes;
735             use MyApp::Types qw/Int/;
736              
737             extends 'Catalyst::Controller';
738              
739             sub chain_base :Chained(/) CaptureArgs(1) { }
740              
741             sub any_priority_chain :Chained(chain_base) PathPart('') Args(1) { }
742              
743             sub int_priority_chain :Chained(chain_base) PathPart('') Args(Int) { }
744              
745             If you use a reference type constraint in CaptureArgs, it must be a type
746             like Tuple in L<Types::Standard> that allows us to determine the number of
747             args to match. Otherwise this will raise an error during startup.
748              
749             See L<Catalyst::RouteMatching> for more.
750              
751             =item Args
752              
753             By default, endpoints receive the rest of the arguments in the path. You
754             can tell Catalyst through C<:Args> explicitly how many arguments your
755             endpoint expects, just like you can with C<:CaptureArgs>. Note that this
756             also affects whether this chain is invoked on a request. A chain with an
757             endpoint specifying one argument will only match if exactly one argument
758             exists in the path.
759              
760             You can specify an exact number of arguments like C<:Args(3)>, including
761             C<0>. If you just say C<:Args> without any arguments, it is the same as
762             leaving it out altogether: The chain is matched regardless of the number
763             of path parts after the endpoint.
764              
765             Just as with C<:CaptureArgs>, the arguments get passed to the action in
766             C<@_> after the context object. They can also be reached through
767             C<< $c->request->arguments >>.
768              
769             You should see 'Args' in L<Catalyst::Controller> for more details on using
770             type constraints in your Args declarations.
771              
772             =back
773              
774             =head2 Auto actions, dispatching and forwarding
775              
776             Note that the list of C<auto> actions called depends on the private path
777             of the endpoint of the chain, not on the chained actions way. The
778             C<auto> actions will be run before the chain dispatching begins. In
779             every other aspect, C<auto> actions behave as documented.
780              
781             The C<forward>ing to other actions does just what you would expect. i.e.
782             only the target action is run. The actions that that action is chained
783             to are not run.
784             If you C<detach> out of a chain, the rest of the chain will not get
785             called after the C<detach>.
786              
787             =head2 match_captures
788              
789             A method which can optionally be implemented by actions to
790             stop chain matching.
791              
792             See L<Catalyst::Action> for further details.
793              
794             =head1 AUTHORS
795              
796             Catalyst Contributors, see Catalyst.pm
797              
798             =head1 COPYRIGHT
799              
800             This library is free software. You can redistribute it and/or modify it under
801             the same terms as Perl itself.
802              
803             =cut
804              
805             1;