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