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