Merge branch 'abort-chain-doc-and-test' of https://github.com/melmothx/catalyst-runti...
[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};
5dd46e24 101
102 my @parts;
103 if($endpoint->has_args_constraints) {
104 @parts = map { "{$_}" } $endpoint->all_args_constraints;
105 } elsif(defined $endpoint->attributes->{Args}) {
106 @parts = (defined($endpoint->attributes->{Args}[0]) ? (("*") x $args) : '...');
107 }
108
d34667c3 109 my @parents = ();
792b40ac 110 my $parent = "DUMMY";
ba3f8a81 111 my $extra = $self->_list_extra_http_methods($endpoint);
e72a3cd6 112 my $consumes = $self->_list_extra_consumes($endpoint);
342d2169 113 my $scheme = $self->_list_extra_scheme($endpoint);
792b40ac 114 my $curr = $endpoint;
115 while ($curr) {
ffca3e96 116 if (my $cap = $curr->list_extra_info->{CaptureArgs}) {
5dd46e24 117 if($curr->has_captures_constraints) {
118 my $names = join '/', map { "{$_}" } $curr->all_captures_constraints;
119 unshift(@parts, $names);
120 } else {
121 unshift(@parts, (("*") x $cap));
122 }
792b40ac 123 }
77771155 124 if (my $pp = $curr->attributes->{PathPart}) {
792b40ac 125 unshift(@parts, $pp->[0])
126 if (defined $pp->[0] && length $pp->[0]);
127 }
5882c86e 128 $parent = $curr->attributes->{Chained}->[0];
be5cb4e4 129 $curr = $self->_actions->{$parent};
d34667c3 130 unshift(@parents, $curr) if $curr;
792b40ac 131 }
007a7ca0 132 if ($parent ne '/') {
133 $has_unattached_actions = 1;
59d5a638 134 $unattached_actions->row('/' . ($parents[0] || $endpoint)->reverse, $parent);
007a7ca0 135 next ENDPOINT;
136 }
d34667c3 137 my @rows;
138 foreach my $p (@parents) {
139 my $name = "/${p}";
ba3f8a81 140
141 if (defined(my $extra = $self->_list_extra_http_methods($p))) {
142 $name = "${extra} ${name}";
143 }
ffca3e96 144 if (defined(my $cap = $p->list_extra_info->{CaptureArgs})) {
90102012 145 if($p->has_captures_constraints) {
146 my $tc = join ',', @{$p->captures_constraints};
147 $name .= " ($tc)";
148 } else {
149 $name .= " ($cap)";
150 }
d34667c3 151 }
e72a3cd6 152 if (defined(my $ct = $p->list_extra_info->{Consumes})) {
153 $name .= ' :'.$ct;
154 }
342d2169 155 if (defined(my $s = $p->list_extra_info->{Scheme})) {
156 $scheme = uc $s;
157 }
e72a3cd6 158
d34667c3 159 unless ($p eq $parents[0]) {
160 $name = "-> ${name}";
161 }
162 push(@rows, [ '', $name ]);
163 }
90102012 164
7e8c3af5 165 my $endpoint_arg_info = $endpoint;
79b7db20 166 if($endpoint->has_args_constraints) {
167 my $tc = join ',', @{$endpoint->args_constraints};
5dd46e24 168 $endpoint_arg_info .= " ($tc)";
79b7db20 169 } else {
5dd46e24 170 $endpoint_arg_info .= defined($endpoint->attributes->{Args}[0]) ? " ($args)" : " (...)";
90102012 171 }
5dd46e24 172 push(@rows, [ '', (@rows ? "=> " : '').($extra ? "$extra " : ''). ($scheme ? "$scheme: ":'')."/${endpoint_arg_info}". ($consumes ? " :$consumes":"" ) ]);
0ca510f0 173 my @display_parts = map { $_ =~s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg; decode_utf8 $_ } @parts;
174 $rows[0][0] = join('/', '', @display_parts) || '/';
d34667c3 175 $paths->row(@$_) for @rows;
792b40ac 176 }
177
1cf0345b 178 $c->log->debug( "Loaded Chained actions:\n" . $paths->draw . "\n" );
007a7ca0 179 $c->log->debug( "Unattached Chained actions:\n", $unattached_actions->draw . "\n" )
180 if $has_unattached_actions;
792b40ac 181}
141459fa 182
ba3f8a81 183sub _list_extra_http_methods {
184 my ( $self, $action ) = @_;
185 return unless defined $action->list_extra_info->{HTTP_METHODS};
186 return join(', ', @{$action->list_extra_info->{HTTP_METHODS}});
e72a3cd6 187
ba3f8a81 188}
189
e72a3cd6 190sub _list_extra_consumes {
191 my ( $self, $action ) = @_;
192 return unless defined $action->list_extra_info->{CONSUMES};
193 return join(', ', @{$action->list_extra_info->{CONSUMES}});
194}
195
342d2169 196sub _list_extra_scheme {
197 my ( $self, $action ) = @_;
198 return unless defined $action->list_extra_info->{Scheme};
199 return uc $action->list_extra_info->{Scheme};
200}
e72a3cd6 201
141459fa 202=head2 $self->match( $c, $path )
203
05a90578 204Calls C<recurse_match> to see if a chain matches the C<$path>.
141459fa 205
206=cut
207
208sub match {
209 my ( $self, $c, $path ) = @_;
210
e5ecd5bc 211 my $request = $c->request;
212 return 0 if @{$request->args};
141459fa 213
214 my @parts = split('/', $path);
215
6365b527 216 my ($chain, $captures, $parts) = $self->recurse_match($c, '/', \@parts);
634780e0 217
218 if ($parts && @$parts) {
219 for my $arg (@$parts) {
220 $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
221 push @{$request->args}, $arg;
222 }
223 }
141459fa 224
225 return 0 unless $chain;
226
227 my $action = Catalyst::ActionChain->from_chain($chain);
228
e5ecd5bc 229 $request->action("/${action}");
230 $request->match("/${action}");
231 $request->captures($captures);
141459fa 232 $c->action($action);
233 $c->namespace( $action->namespace );
234
235 return 1;
236}
237
238=head2 $self->recurse_match( $c, $parent, \@path_parts )
239
05a90578 240Recursive search for a matching chain.
141459fa 241
242=cut
243
244sub recurse_match {
245 my ( $self, $c, $parent, $path_parts ) = @_;
be5cb4e4 246 my $children = $self->_children_of->{$parent};
141459fa 247 return () unless $children;
6b495723 248 my $best_action;
141459fa 249 my @captures;
1b04b972 250 TRY: foreach my $try_part (sort { length($b) <=> length($a) }
cdc97b63 251 keys %$children) {
1b04b972 252 # $b then $a to try longest part first
141459fa 253 my @parts = @$path_parts;
254 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
792b40ac 258 @parts, 0, scalar(@{[split('/', $try_part)]})
259 ))); # @{[]} to avoid split to @_
141459fa 260 }
261 my @try_actions = @{$children->{$try_part}};
262 TRY_ACTION: foreach my $action (@try_actions) {
905679b8 263
264
1c34f703 265 if (my $capture_attr = $action->attributes->{CaptureArgs}) {
a82c96cf 266 my $capture_count = $action->number_of_captures|| 0;
f505df49 267
268 # Short-circuit if not enough remaining parts
3f91cb25 269 next TRY_ACTION unless @parts >= $capture_count;
f505df49 270
141459fa 271 my @captures;
272 my @parts = @parts; # localise
7a7ac23c 273
1c34f703 274 # strip CaptureArgs into list
3f91cb25 275 push(@captures, splice(@parts, 0, $capture_count));
7a7ac23c 276
1279064a 277 # check if the action may fit, depending on a given test by the app
a82c96cf 278 next TRY_ACTION unless $action->match_captures($c, \@captures);
1279064a 279
141459fa 280 # try the remaining parts against children of this action
0c78acb6 281 my ($actions, $captures, $action_parts, $n_pathparts) = $self->recurse_match(
141459fa 282 $c, '/'.$action->reverse, \@parts
283 );
2f381252 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 if ($actions &&
288 (!$best_action ||
f4dbb497 289 $#$action_parts < $#{$best_action->{parts}} ||
2f381252 290 ($#$action_parts == $#{$best_action->{parts}} &&
0c78acb6 291 $#$captures < $#{$best_action->{captures}} &&
292 $n_pathparts > $best_action->{n_pathparts}))) {
293 my @pathparts = split /\//, $action->attributes->{PathPart}->[0];
6b495723 294 $best_action = {
295 actions => [ $action, @$actions ],
296 captures=> [ @captures, @$captures ],
0c78acb6 297 parts => $action_parts,
298 n_pathparts => scalar(@pathparts) + $n_pathparts,
299 };
6b495723 300 }
301 }
302 else {
7a7ac23c 303 {
304 local $c->req->{arguments} = [ @{$c->req->args}, @parts ];
305 next TRY_ACTION unless $action->match($c);
306 }
5c6d829d 307 my $args_attr = $action->attributes->{Args}->[0];
5dd46e24 308 my $args_count = $action->comparable_arg_number;
0c78acb6 309 my @pathparts = split /\//, $action->attributes->{PathPart}->[0];
953c176d 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
b0ad47c1 313 # OR No parts and this expects 0
953c176d 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
219b4f88 316 if (
317 !$best_action ||
953c176d 318 @parts < @{$best_action->{parts}} ||
219b4f88 319 (
320 !@parts &&
321 defined($args_attr) &&
09914a47 322 (
2a1c08c0 323 $args_count eq "0" &&
7a504990 324 (
325 ($c->config->{use_chained_args_0_special_case}||0) ||
326 (
2a1c08c0 327 exists($best_action->{args_count}) && defined($best_action->{args_count}) ?
328 ($best_action->{args_count} ne 0) : 1
7a504990 329 )
330 )
09914a47 331 )
219b4f88 332 )
333 ){
6b495723 334 $best_action = {
335 actions => [ $action ],
336 captures=> [],
0c78acb6 337 parts => \@parts,
2a1c08c0 338 args_count => $args_count,
0c78acb6 339 n_pathparts => scalar(@pathparts),
340 };
953c176d 341 }
141459fa 342 }
343 }
344 }
0c78acb6 345 return @$best_action{qw/actions captures parts n_pathparts/} if $best_action;
141459fa 346 return ();
347}
348
349=head2 $self->register( $c, $action )
350
05a90578 351Calls register_path for every Path attribute for the given $action.
141459fa 352
353=cut
354
355sub register {
356 my ( $self, $c, $action ) = @_;
357
1dc8af44 358 my @chained_attr = @{ $action->attributes->{Chained} || [] };
141459fa 359
1dc8af44 360 return 0 unless @chained_attr;
141459fa 361
2f381252 362 if (@chained_attr > 1) {
141459fa 363 Catalyst::Exception->throw(
5882c86e 364 "Multiple Chained attributes not supported registering ${action}"
141459fa 365 );
366 }
13c6b4cc 367 my $chained_to = $chained_attr[0];
141459fa 368
13c6b4cc 369 Catalyst::Exception->throw(
370 "Actions cannot chain to themselves registering /${action}"
371 ) if ($chained_to eq '/' . $action);
372
373 my $children = ($self->_children_of->{ $chained_to } ||= {});
141459fa 374
375 my @path_part = @{ $action->attributes->{PathPart} || [] };
376
09461385 377 my $part = $action->name;
141459fa 378
09461385 379 if (@path_part == 1 && defined $path_part[0]) {
380 $part = $path_part[0];
141459fa 381 } elsif (@path_part > 1) {
382 Catalyst::Exception->throw(
f3414019 383 "Multiple PathPart attributes not supported registering " . $action->reverse()
141459fa 384 );
385 }
386
8a6a6581 387 if ($part =~ m(^/)) {
388 Catalyst::Exception->throw(
f3414019 389 "Absolute parameters to PathPart not allowed registering " . $action->reverse()
8a6a6581 390 );
391 }
392
0ca510f0 393 my $encoded_part = URI->new($part)->canonical;
394 $encoded_part =~ s{(?<=[^/])/+\z}{};
792b40ac 395
0ca510f0 396 $action->attributes->{PathPart} = [ $encoded_part ];
397
398 unshift(@{ $children->{$encoded_part} ||= [] }, $action);
141459fa 399
be5cb4e4 400 $self->_actions->{'/'.$action->reverse} = $action;
792b40ac 401
2e29aec2 402 if (exists $action->attributes->{Args} and exists $action->attributes->{CaptureArgs}) {
403 Catalyst::Exception->throw(
bc71493a 404 "Combining Args and CaptureArgs attributes not supported registering " .
405 $action->reverse()
2e29aec2 406 );
dd97c1ac 407 }
408
1c34f703 409 unless ($action->attributes->{CaptureArgs}) {
be5cb4e4 410 unshift(@{ $self->_endpoints }, $action);
792b40ac 411 }
412
413 return 1;
141459fa 414}
415
416=head2 $self->uri_for_action($action, $captures)
417
05a90578 418Get the URI part for the action, using C<$captures> to fill
419the capturing parts.
141459fa 420
421=cut
422
423sub uri_for_action {
424 my ( $self, $action, $captures ) = @_;
425
5882c86e 426 return undef unless ($action->attributes->{Chained}
8b13f357 427 && !$action->attributes->{CaptureArgs});
792b40ac 428
429 my @parts = ();
430 my @captures = @$captures;
431 my $parent = "DUMMY";
432 my $curr = $action;
b6847871 433 # If this is an action chain get the last action in the chain
434 if($curr->can('chain') ) {
435 $curr = ${$curr->chain}[-1];
436 }
792b40ac 437 while ($curr) {
b6847871 438 if (my $cap = $curr->number_of_captures) {
439 return undef unless @captures >= $cap; # not enough captures
440 if ($cap) {
441 unshift(@parts, splice(@captures, -$cap));
8b13f357 442 }
792b40ac 443 }
77771155 444 if (my $pp = $curr->attributes->{PathPart}) {
792b40ac 445 unshift(@parts, $pp->[0])
8b13f357 446 if (defined($pp->[0]) && length($pp->[0]));
792b40ac 447 }
5882c86e 448 $parent = $curr->attributes->{Chained}->[0];
be5cb4e4 449 $curr = $self->_actions->{$parent};
141459fa 450 }
792b40ac 451
452 return undef unless $parent eq '/'; # fail for dangling action
453
454 return undef if @captures; # fail for too many captures
455
456 return join('/', '', @parts);
59d5a638 457
141459fa 458}
459
ae0e35ee 460=head2 $c->expand_action($action)
461
b0ad47c1 462Return a list of actions that represents a chained action. See
ae0e35ee 463L<Catalyst::Dispatcher> for more info. You probably want to
464use the expand_action it provides rather than this directly.
465
466=cut
467
52f71256 468sub expand_action {
469 my ($self, $action) = @_;
470
471 return unless $action->attributes && $action->attributes->{Chained};
472
473 my @chain;
474 my $curr = $action;
475
476 while ($curr) {
477 push @chain, $curr;
478 my $parent = $curr->attributes->{Chained}->[0];
479 $curr = $self->_actions->{$parent};
480 }
481
482 return Catalyst::ActionChain->from_chain([reverse @chain]);
483}
484
e5ecd5bc 485__PACKAGE__->meta->make_immutable;
f4de8c99 4861;
e5ecd5bc 487
05a90578 488=head1 USAGE
489
490=head2 Introduction
491
492The C<Chained> attribute allows you to chain public path parts together
67869327 493by their private names. A chain part's path can be specified with
494C<PathPart> and can be declared to expect an arbitrary number of
495arguments. The endpoint of the chain specifies how many arguments it
496gets through the C<Args> attribute. C<:Args(0)> would be none at all,
497C<:Args> without an integer would be unlimited. The path parts that
498aren't endpoints are using C<CaptureArgs> to specify how many parameters
499they expect to receive. As an example setup:
05a90578 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
521The debug output provides a separate table for chained actions, showing
67869327 522the whole chain as it would match and the actions it contains. Here's an
523example of the startup output with our actions above:
05a90578 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
67869327 535As you can see, Catalyst only deals with chains as whole paths and
536builds one for each endpoint, which are the actions with C<:Chained> but
537without C<:CaptureArgs>.
05a90578 538
539Let's assume this application gets a request at the path
67869327 540C</hello/23/world/12>. What happens then? First, Catalyst will dispatch
541to the C<hello> action and pass the value C<23> as an argument to it
542after the context. It does so because we have previously used
543C<:CaptureArgs(1)> to declare that it has one path part after itself as
544its argument. We told Catalyst that this is the beginning of the chain
545by specifying C<:Chained('/')>. Also note that instead of saying
546C<:PathPart('hello')> we could also just have said C<:PathPart>, as it
547defaults to the name of the action.
05a90578 548
549After C<hello> has run, Catalyst goes on to dispatch to the C<world>
67869327 550action. This is the last action to be called: Catalyst knows this is an
551endpoint because we did not specify a C<:CaptureArgs>
552attribute. Nevertheless we specify that this action expects an argument,
553but at this point we're using C<:Args(1)> to do that. We could also have
554said C<:Args> or left it out altogether, which would mean this action
555would get all arguments that are there. This action's C<:Chained>
556attribute says C<hello> and tells Catalyst that the C<hello> action in
557the current controller is its parent.
05a90578 558
559With this we have built a chain consisting of two public path parts.
67869327 560C<hello> captures one part of the path as its argument, and also
561specifies the path root as its parent. So this part is
562C</hello/$arg>. The next part is the endpoint C<world>, expecting one
563argument. It sums up to the path part C<world/$arg>. This leads to a
564complete chain of C</hello/$arg/world/$arg> which is matched against the
565requested paths.
566
567This example application would, if run and called by e.g.
568C</hello/23/world/12>, set the stash value C<message> to "Hello" and the
569value C<arg_sum> to "23". The C<world> action would then append "World!"
570to C<message> and add C<12> to the stash's C<arg_sum> value. For the
571sake of simplicity no view is shown. Instead we just put the values of
572the stash into our body. So the output would look like:
05a90578 573
574 Hello World!
575 35
576
67869327 577And our test server would have given us this debugging output for the
05a90578 578request:
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
67869327 593What would be common uses of this dispatch technique? It gives the
594possibility to split up logic that contains steps that each depend on
595each other. An example would be, for example, a wiki path like
05a90578 596C</wiki/FooBarPage/rev/23/view>. This chain can be easily built with
597these 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 ) = @_;
67869327 607 # use the page object in the stash to get at its
05a90578 608 # revision with number $revision_id
609 }
610
611 sub view : PathPart Chained('rev') Args(0) {
612 my ( $self, $c ) = @_;
67869327 613 # display the revision in our stash. Another option
05a90578 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
67869327 620It would now be possible to add other endpoints, for example C<restore>
621to restore this specific revision as the current state.
05a90578 622
67869327 623You don't have to put all the chained actions in one controller. The
624specification of the parent through C<:Chained> also takes an absolute
625action path as its argument. Just specify it with a leading C</>.
05a90578 626
627If you want, for example, to have actions for the public paths
67869327 628C</foo/12/edit> and C</foo/12>, just specify two actions with
05a90578 629C<:PathPart('foo')> and C<:Chained('/')>. The handler for the former
67869327 630path needs a C<:CaptureArgs(1)> attribute and a endpoint with
05a90578 631C<:PathPart('edit')> and C<:Chained('foo')>. For the latter path give
632the action just a C<:Args(1)> to mark it as endpoint. This sums up to
633this 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
b0ad47c1 646Here's a more detailed specification of the attributes belonging to
05a90578 647C<:Chained>:
648
649=head2 Attributes
650
651=over 8
652
653=item PathPart
654
655Sets the name of this part of the chain. If it is specified without
656arguments, it takes the name of the action as default. So basically
657C<sub foo :PathPart> and C<sub foo :PathPart('foo')> are identical.
658This can also contain slashes to bind to a deeper level. An action
659with C<sub bar :PathPart('foo/bar') :Chained('/')> would bind to
660C</foo/bar/...>. If you don't specify C<:PathPart> it has the same
661effect as using C<:PathPart>, it would default to the action name.
662
d21a2b27 663=item PathPrefix
664
665Sets PathPart to the path_prefix of the current controller.
666
05a90578 667=item Chained
668
669Has to be specified for every child in the chain. Possible values are
d21a2b27 670absolute and relative private action paths or a single slash C</> to
671tell Catalyst that this is the root of a chain. The attribute
672C<:Chained> without arguments also defaults to the C</> behavior.
673Relative action paths may use C<../> to refer to actions in parent
674controllers.
05a90578 675
67869327 676Because you can specify an absolute path to the parent action, it
677doesn't matter to Catalyst where that parent is located. So, if your
678design requests it, you can redispatch a chain through any controller or
679namespace you want.
05a90578 680
681Another interesting possibility gives C<:Chained('.')>, which chains
67869327 682itself to an action with the path of the current controller's namespace.
05a90578 683For 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
691This builds up a chain like C</bar/*/baz/*>. The specification of C<.>
67869327 692as the argument to Chained here chains the C<baz> action to an action
693with the path of the current controller namespace, namely
694C</foo/bar>. That action chains directly to C</>, so the C</bar/*/baz/*>
695chain comes out as the end product.
05a90578 696
d21a2b27 697=item ChainedParent
698
699Chains an action to another action with the same name in the parent
700controller. For Example:
701
702 # in MyApp::Controller::Foo
703 sub bar : Chained CaptureArgs(1) { ... }
704
705 # in MyApp::Controller::Foo::Moo
706 sub bar : ChainedParent Args(1) { ... }
707
708This builds a chain like C</bar/*/bar/*>.
709
05a90578 710=item CaptureArgs
711
67869327 712Must be specified for every part of the chain that is not an
05a90578 713endpoint. With this attribute Catalyst knows how many of the following
67869327 714parts of the path (separated by C</>) this action wants to capture as
715its arguments. If it doesn't expect any, just specify
716C<:CaptureArgs(0)>. The captures get passed to the action's C<@_> right
717after the context, but you can also find them as array references in
eb67e0f6 718C<< $c->request->captures->[$level] >>. The C<$level> is the
05a90578 719level of the action in the chain that captured the parts of the path.
720
67869327 721An action that is part of a chain (that is, one that has a C<:Chained>
722attribute) but has no C<:CaptureArgs> attribute is treated by Catalyst
723as a chain end.
05a90578 724
480d94b5 725Allowed values for CaptureArgs is a single integer (CaptureArgs(2), meaning two
726allowed) or you can declare a L<Moose>, L<MooseX::Types> or L<Type::Tiny>
727named constraint such as CaptureArgs(Int,Str) would require two args with
728the first being a Integer and the second a string. You may declare your own
729custom 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
d9f0a350 745If you use a reference type constraint in CaptureArgs, it must be a type
746like Tuple in L<Types::Standard> that allows us to determine the number of
747args to match. Otherwise this will raise an error during startup.
748
480d94b5 749See L<Catalyst::RouteMatching> for more.
750
05a90578 751=item Args
752
753By default, endpoints receive the rest of the arguments in the path. You
754can tell Catalyst through C<:Args> explicitly how many arguments your
755endpoint expects, just like you can with C<:CaptureArgs>. Note that this
67869327 756also affects whether this chain is invoked on a request. A chain with an
05a90578 757endpoint specifying one argument will only match if exactly one argument
758exists in the path.
759
760You can specify an exact number of arguments like C<:Args(3)>, including
761C<0>. If you just say C<:Args> without any arguments, it is the same as
67869327 762leaving it out altogether: The chain is matched regardless of the number
05a90578 763of path parts after the endpoint.
764
67869327 765Just as with C<:CaptureArgs>, the arguments get passed to the action in
05a90578 766C<@_> after the context object. They can also be reached through
eb67e0f6 767C<< $c->request->arguments >>.
05a90578 768
d9f0a350 769You should see 'Args' in L<Catalyst::Controller> for more details on using
770type constraints in your Args declarations.
771
05a90578 772=back
773
67869327 774=head2 Auto actions, dispatching and forwarding
05a90578 775
776Note that the list of C<auto> actions called depends on the private path
67869327 777of the endpoint of the chain, not on the chained actions way. The
778C<auto> actions will be run before the chain dispatching begins. In
779every other aspect, C<auto> actions behave as documented.
05a90578 780
64111aca 781The C<forward>ing to other actions does just what you would expect. i.e.
e1207438 782only the target action is run. The actions that that action is chained
783to are not run.
784If you C<detach> out of a chain, the rest of the chain will not get
785called after the C<detach>.
05a90578 786
22faeff5 787=head2 match_captures
788
789A method which can optionally be implemented by actions to
790stop chain matching.
791
792See L<Catalyst::Action> for further details.
793
2f381252 794=head1 AUTHORS
141459fa 795
2f381252 796Catalyst Contributors, see Catalyst.pm
141459fa 797
798=head1 COPYRIGHT
799
536bee89 800This library is free software. You can redistribute it and/or modify it under
141459fa 801the same terms as Perl itself.
802
803=cut
804
8051;