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