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