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