Moved Chained intro to dispatch type and made recursion test more sane.
Matt S Trout [Thu, 22 Jun 2006 14:54:10 +0000 (14:54 +0000)]
r10178@cain (orig r4435):  phaylon | 2006-06-21 20:58:51 +0000

lib/Catalyst/DispatchType/Chained.pm
lib/Catalyst/Manual/Intro.pod
t/live_component_controller_action_chained.t

index 1d7783b..2fb0a0b 100644 (file)
@@ -14,10 +14,22 @@ Catalyst::DispatchType::Chained - Path Part DispatchType
 
 =head1 SYNOPSIS
 
-See L<Catalyst>.
+  #   root action - captures one argument after it
+  sub foo_setup : Chained('/') PathPart('foo') CaptureArgs(1) {
+      my ( $self, $c, $foo_arg ) = @_;
+      ...
+  }
+
+  #   child action endpoint - takes one argument
+  sub bar : Chained('foo_setup') Args(1) {
+      my ( $self, $c, $bar_arg ) = @_;
+      ...
+  }
 
 =head1 DESCRIPTION
 
+See L</USAGE>.
+
 =head1 METHODS
 
 =head2 $self->list($c)
@@ -78,7 +90,7 @@ sub list {
 
 =head2 $self->match( $c, $path )
 
-Matt is an idiot and hasn't documented this yet.
+Calls C<recurse_match> to see if a chain matches the C<$path>.
 
 =cut
 
@@ -106,7 +118,7 @@ sub match {
 
 =head2 $self->recurse_match( $c, $parent, \@path_parts )
 
-Matt is an idiot and hasn't documented this yet.
+Recursive search for a matching chain.
 
 =cut
 
@@ -157,7 +169,7 @@ sub recurse_match {
 
 =head2 $self->register( $c, $action )
 
-Matt is an idiot and hasn't documented this yet.
+Calls register_path for every Path attribute for the given $action.
 
 =cut
 
@@ -223,7 +235,8 @@ sub register {
 
 =head2 $self->uri_for_action($action, $captures)
 
-Matt is an idiot and hasn't documented this yet.
+Get the URI part for the action, using C<$captures> to fill
+the capturing parts.
 
 =cut
 
@@ -258,6 +271,255 @@ sub uri_for_action {
    
 }
 
+=head1 USAGE
+
+=head2 Introduction
+
+The C<Chained> attribute allows you to chain public path parts together
+by their private names. A chain part's path can be specified with C<PathPart>
+and can be declared to expect an arbitrary number of arguments. The endpoint
+of the chain specifies how many arguments it gets through the C<Args>
+attribute. C<:Args(0)> would be none at all, C<:Args> without an integer
+would be unlimited. The path parts that aren't endpoints are using
+C<CaptureArgs> to specify how many parameters they expect to receive. As an
+example setup:
+
+  package MyApp::Controller::Greeting;
+  use base qw/ Catalyst::Controller /;
+
+  #   this is the beginning of our chain
+  sub hello : PathPart('hello') Chained('/') CaptureArgs(1) {
+      my ( $self, $c, $integer ) = @_;
+      $c->stash->{ message } = "Hello ";
+      $c->stash->{ arg_sum } = $integer;
+  }
+
+  #   this is our endpoint, because it has no :CaptureArgs
+  sub world : PathPart('world') Chained('hello') Args(1) {
+      my ( $self, $c, $integer ) = @_;
+      $c->stash->{ message } .= "World!";
+      $c->stash->{ arg_sum } += $integer;
+
+      $c->response->body( join "<br/>\n" =>
+          $c->stash->{ message }, $c->stash->{ arg_sum } );
+  }
+
+The debug output provides a separate table for chained actions, showing
+the whole chain as it would match and the actions it contains. Here's
+an example of the startup output with our actions above:
+
+  ...
+  [debug] Loaded Path Part actions:
+  .-----------------------+------------------------------.
+  | Path Spec             | Private                      |
+  +-----------------------+------------------------------+
+  | /hello/*/world/*      | /greeting/hello (1)          |
+  |                       | => /greeting/world           |
+  '-----------------------+------------------------------'
+  ...
+
+As you can see, Catalyst only deals with chains as whole path and 
+builds one for each endpoint, which are the actions with C<:Chained>
+but without C<:CaptureArgs>.
+
+Let's assume this application gets a request at the path
+C</hello/23/world/12>, what happens then? First, Catalyst will dispatch
+to the C<hello> action and pass the value C<23> as argument to it after
+the context. It does so because we have previously used C<:CaptureArgs(1)>
+to declare that it has one path part after itself as it's argument. We
+told Catalyst that this is the beginning of the chain by specifying
+C<:Chained('/')>. Also note that instead of saying C<:PathPart('hello')>
+we could also just have said C<:PathPart>, as it defaults to the name of
+the action.
+
+After C<hello> has run, Catalyst goes on to dispatch to the C<world>
+action. This is the last action to be called, as Catalyst knows this
+is an endpoint because we specified no C<:CaptureArgs> attribute. Nevertheless
+we specify that this action expects an argument, but at this point we're
+using C<:Args(1)> to do that. We could also have said C<:Args> or leave 
+it out alltogether, which would mean this action gets all arguments that
+are there. This action's C<:Chained> attribute says C<hello> and tells
+Catalyst that the C<hello> action in the current controller is it's 
+parent.
+
+With this we have built a chain consisting of two public path parts.
+C<hello> captures one part of the path as it's argument, and also specifies
+the path root as it's parent. So this part is C</hello/$arg>. The next part
+is the endpoint C<world>, expecting one argument. It sums up to the path
+part C<world/$arg>. This leads to a complete chain of 
+C</hello/$arg/world/$arg> which is matched against the requested paths.
+
+This example application would, if run and called by e.g. 
+C</hello/23/world/12>, set the stash value C<message> to C<Hello > and
+the value C<arg_sum> to C<23>. The C<world> action would then append
+C<World!> to C<message> and add C<12> to the stash's C<arg_sum> value.
+For the sake of simplicity no view is shown. Instead we just put the
+values of the stash into our body. So the output would look like:
+
+  Hello World!
+  35
+
+And our test server would've given us this debugging output for the
+request:
+
+  ...
+  [debug] "GET" request for "hello/23/world/12" from "127.0.0.1"
+  [debug] Path is "/greeting/world"
+  [debug] Arguments are "12"
+  [info] Request took 0.164113s (6.093/s)
+  .------------------------------------------+-----------.
+  | Action                                   | Time      |
+  +------------------------------------------+-----------+
+  | /greeting/hello                          | 0.000029s |
+  | /greeting/world                          | 0.000024s |
+  '------------------------------------------+-----------'
+  ...
+
+What would be common usecases of this dispatching technique? It gives the
+possibility to split up logic that contains steps that each depend on each
+other. An example would be, for example, a wiki path like
+C</wiki/FooBarPage/rev/23/view>. This chain can be easily built with
+these actions:
+
+  sub wiki : PathPart('wiki') Chained('/') CaptureArgs(1) {
+      my ( $self, $c, $page_name ) = @_;
+      #  load the page named $page_name and put the object
+      #  into the stash
+  }
+
+  sub rev : PathPart('rev') Chained('wiki') CaptureArgs(1) {
+      my ( $self, $c, $revision_id ) = @_;
+      #  use the page object in the stash to get at it's
+      #  revision with number $revision_id
+  }
+
+  sub view : PathPart Chained('rev') Args(0) {
+      my ( $self, $c ) = @_;
+      #  display the revision in our stash. An other option
+      #  would be to forward a compatible object to the action
+      #  that displays the default wiki pages, unless we want
+      #  a different interface here, for example restore
+      #  functionality.
+  }
+
+It would now be possible to add other endpoints. For example C<restore> to
+restore this specific revision as current state.
+
+Also, you of course don't have to put all the chained actions in one
+controller. The specification of the parent through C<:Chained> also takes
+an absolute action path as it's argument. Just specify it with a leading
+C</>.
+
+If you want, for example, to have actions for the public paths
+C</foo/12/edit> and C</foo/12>, just specify two actions with 
+C<:PathPart('foo')> and C<:Chained('/')>. The handler for the former
+path needs a C<:CaptureArgs(1)> attribute and a endpoint with 
+C<:PathPart('edit')> and C<:Chained('foo')>. For the latter path give
+the action just a C<:Args(1)> to mark it as endpoint. This sums up to
+this debugging output:
+
+  ...
+  [debug] Loaded Path Part actions:
+  .-----------------------+------------------------------.
+  | Path Spec             | Private                      |
+  +-----------------------+------------------------------+
+  | /foo/*                | /controller/foo_view         |
+  | /foo/*/edit           | /controller/foo_load (1)     |
+  |                       | => /controller/edit          |
+  '-----------------------+------------------------------'
+  ...
+
+Here's a more detailed specification of the attributes belonging to 
+C<:Chained>:
+
+=head2 Attributes
+
+=over 8
+
+=item PathPart
+
+Sets the name of this part of the chain. If it is specified without
+arguments, it takes the name of the action as default. So basically
+C<sub foo :PathPart> and C<sub foo :PathPart('foo')> are identical.
+This can also contain slashes to bind to a deeper level. An action
+with C<sub bar :PathPart('foo/bar') :Chained('/')> would bind to
+C</foo/bar/...>. If you don't specify C<:PathPart> it has the same
+effect as using C<:PathPart>, it would default to the action name.
+
+=item Chained
+
+Has to be specified for every child in the chain. Possible values are
+absolute and relative private action paths, with the relatives pointing
+to the current controller, or a single slash C</> to tell Catalyst that
+this is the root of a chain. The attribute C<:Chained> without aguments
+also defaults to the C</> behaviour.
+
+Due to the fact that you can specify an absolute path to the parent
+action, it doesn't matter to Catalyst where that parent is located. So,
+if your design requests it, you can redispatch a chain through every
+controller or namespace you want.
+
+Another interesting possibility gives C<:Chained('.')>, which chains
+itself to an action with the path of the current controllers namespace.
+For example:
+
+  #   in MyApp::Controller::Foo
+  sub bar : Chained CaptureArgs(1) { ... }
+
+  #   in MyApp::Controller::Foo::Bar
+  sub baz : Chained('.') Args(1) { ... }
+
+This builds up a chain like C</bar/*/baz/*>. The specification of C<.>
+as argument to Chained here chains the C<baz> action to an action with
+the path of the current controller namespace, namely C</foo/bar>. That
+action chains directly to C</>, so the above chain comes out as end
+product.
+
+=item CaptureArgs
+
+Also has to be specified for every part of the chain that is not an
+endpoint. With this attribute Catalyst knows how many of the following
+parts of the path (separated by C</>) this action wants to captures as
+it's arguments. If it doesn't expect any, just specify C<:CaptureArgs(0)>.
+The captures get passed to the action's C<@_> right after the context,
+but you can also find them as array reference in 
+C<$c-E<gt>request-E<gt>captures-E<gt>[$level]>. The C<$level> is the
+level of the action in the chain that captured the parts of the path.
+
+An action that is part of a chain (read: that has a C<:Chained> attribute)
+but has no C<:CaptureArgs> attribute is treated by Catalyst as a chain end.
+
+=item Args
+
+By default, endpoints receive the rest of the arguments in the path. You
+can tell Catalyst through C<:Args> explicitly how many arguments your
+endpoint expects, just like you can with C<:CaptureArgs>. Note that this
+also influences if this chain is invoked on a request. A chain with an
+endpoint specifying one argument will only match if exactly one argument
+exists in the path.
+
+You can specify an exact number of arguments like C<:Args(3)>, including
+C<0>. If you just say C<:Args> without any arguments, it is the same as
+leaving it out alltogether: The chain is matched independent of the number
+of path parts after the endpoint.
+
+Just like with C<:CaptureArgs>, the arguments get passed to the action in
+C<@_> after the context object. They can also be reached through
+C<$c-E<gt>request-E<gt>arguments>.
+
+=back
+
+=head2 auto actions, dispatching and forwarding
+
+Note that the list of C<auto> actions called depends on the private path
+of the endpoint of the chain, not on the chained actions way. The C<auto>
+actions will be run before the chain dispatching begins. In every other
+aspect, C<auto> actions behave as documented.
+
+The C<forward>ing to other actions does just what you would expect. But if
+you C<detach> out of a chain, the rest of the chain will not get called
+after the C<detach> returned.
+
 =head1 AUTHOR
 
 Matt S Trout <mst@shadowcatsystems.co.uk>
index 32e2ba1..44a80b4 100644 (file)
@@ -417,249 +417,6 @@ C<$c-E<gt>req-E<gt>captures-E<gt>[0]> would be "23". If you want to pass
 arguments at the end of your URL, you must use regex action keys. See
 L</URL Path Handling> below.
 
-=item * B<Chained>
-
-The C<Chained> attribute allows you to chain public path parts together
-by their private names. A chain part's path can be specified with C<PathPart>
-and can be declared to expect an arbitrary number of arguments. The endpoint
-of the chain specifies how many arguments it gets through the C<Args>
-attribute. C<:Args(0)> would be none at all, C<:Args> without an integer
-would be unlimited. The path parts that aren't endpoints are using
-C<CaptureArgs> to specify how many parameters they expect to receive. As an
-example setup:
-
-  package MyApp::Controller::Greeting;
-  use base qw/ Catalyst::Controller /;
-
-  #   this is the beginning of our chain
-  sub hello : PathPart('hello') Chained('/') CaptureArgs(1) {
-      my ( $self, $c, $integer ) = @_;
-      $c->stash->{ message } = "Hello ";
-      $c->stash->{ arg_sum } = $integer;
-  }
-
-  #   this is our endpoint, because it has no :CaptureArgs
-  sub world : PathPart('world') Chained('hello') Args(1) {
-      my ( $self, $c, $integer ) = @_;
-      $c->stash->{ message } .= "World!";
-      $c->stash->{ arg_sum } += $integer;
-
-      $c->response->body( join "<br/>\n" =>
-          $c->stash->{ message }, $c->stash->{ arg_sum } );
-  }
-
-The debug output provides a separate table for chained actions, showing
-the whole chain as it would match and the actions it contains. Here's
-an example of the startup output with our actions above:
-
-  ...
-  [debug] Loaded Path Part actions:
-  .-----------------------+------------------------------.
-  | Path Spec             | Private                      |
-  +-----------------------+------------------------------+
-  | /hello/*/world/*      | /greeting/hello (1)          |
-  |                       | => /greeting/world           |
-  '-----------------------+------------------------------'
-  ...
-
-As you can see, Catalyst only deals with chains as whole path and 
-builds one for each endpoint, which are the actions with C<:Chained>
-but without C<:CaptureArgs>.
-
-Let's assume this application gets a request at the path
-C</hello/23/world/12>, what happens then? First, Catalyst will dispatch
-to the C<hello> action and pass the value C<23> as argument to it after
-the context. It does so because we have previously used C<:CaptureArgs(1)>
-to declare that it has one path part after itself as it's argument. We
-told Catalyst that this is the beginning of the chain by specifying
-C<:Chained('/')>. Also note that instead of saying C<:PathPart('hello')>
-we could also just have said C<:PathPart>, as it defaults to the name of
-the action.
-
-After C<hello> has run, Catalyst goes on to dispatch to the C<world>
-action. This is the last action to be called, as Catalyst knows this
-is an endpoint because we specified no C<:CaptureArgs> attribute. Nevertheless
-we specify that this action expects an argument, but at this point we're
-using C<:Args(1)> to do that. We could also have said C<:Args> or leave 
-it out alltogether, which would mean this action gets all arguments that
-are there. This action's C<:Chained> attribute says C<hello> and tells
-Catalyst that the C<hello> action in the current controller is it's 
-parent.
-
-With this we have built a chain consisting of two public path parts.
-C<hello> captures one part of the path as it's argument, and also specifies
-the path root as it's parent. So this part is C</hello/$arg>. The next part
-is the endpoint C<world>, expecting one argument. It sums up to the path
-part C<world/$arg>. This leads to a complete chain of 
-C</hello/$arg/world/$arg> which is matched against the requested paths.
-
-This example application would, if run and called by e.g. 
-C</hello/23/world/12>, set the stash value C<message> to C<Hello > and
-the value C<arg_sum> to C<23>. The C<world> action would then append
-C<World!> to C<message> and add C<12> to the stash's C<arg_sum> value.
-For the sake of simplicity no view is shown. Instead we just put the
-values of the stash into our body. So the output would look like:
-
-  Hello World!
-  35
-
-And our test server would've given us this debugging output for the
-request:
-
-  ...
-  [debug] "GET" request for "hello/23/world/12" from "127.0.0.1"
-  [debug] Path is "/greeting/world"
-  [debug] Arguments are "12"
-  [info] Request took 0.164113s (6.093/s)
-  .------------------------------------------+-----------.
-  | Action                                   | Time      |
-  +------------------------------------------+-----------+
-  | /greeting/hello                          | 0.000029s |
-  | /greeting/world                          | 0.000024s |
-  '------------------------------------------+-----------'
-  ...
-
-What would be common usecases of this dispatching technique? It gives the
-possibility to split up logic that contains steps that each depend on each
-other. An example would be, for example, a wiki path like
-C</wiki/FooBarPage/rev/23/view>. This chain can be easily built with
-these actions:
-
-  sub wiki : PathPart('wiki') Chained('/') CaptureArgs(1) {
-      my ( $self, $c, $page_name ) = @_;
-      #  load the page named $page_name and put the object
-      #  into the stash
-  }
-
-  sub rev : PathPart('rev') Chained('wiki') CaptureArgs(1) {
-      my ( $self, $c, $revision_id ) = @_;
-      #  use the page object in the stash to get at it's
-      #  revision with number $revision_id
-  }
-
-  sub view : PathPart Chained('rev') Args(0) {
-      my ( $self, $c ) = @_;
-      #  display the revision in our stash. An other option
-      #  would be to forward a compatible object to the action
-      #  that displays the default wiki pages, unless we want
-      #  a different interface here, for example restore
-      #  functionality.
-  }
-
-It would now be possible to add other endpoints. For example C<restore> to
-restore this specific revision as current state.
-
-Also, you of course don't have to put all the chained actions in one
-controller. The specification of the parent through C<:Chained> also takes
-an absolute action path as it's argument. Just specify it with a leading
-C</>.
-
-If you want, for example, to have actions for the public paths
-C</foo/12/edit> and C</foo/12>, just specify two actions with 
-C<:PathPart('foo')> and C<:Chained('/')>. The handler for the former
-path needs a C<:CaptureArgs(1)> attribute and a endpoint with 
-C<:PathPart('edit')> and C<:Chained('foo')>. For the latter path give
-the action just a C<:Args(1)> to mark it as endpoint. This sums up to
-this debugging output:
-
-  ...
-  [debug] Loaded Path Part actions:
-  .-----------------------+------------------------------.
-  | Path Spec             | Private                      |
-  +-----------------------+------------------------------+
-  | /foo/*                | /controller/foo_view         |
-  | /foo/*/edit           | /controller/foo_load (1)     |
-  |                       | => /controller/edit          |
-  '-----------------------+------------------------------'
-  ...
-
-Here's a more detailed specification of the attributes belonging to 
-C<:Chained>:
-
-=over 8
-
-=item PathPart
-
-Sets the name of this part of the chain. If it is specified without
-arguments, it takes the name of the action as default. So basically
-C<sub foo :PathPart> and C<sub foo :PathPart('foo')> are identical.
-This can also contain slashes to bind to a deeper level. An action
-with C<sub bar :PathPart('foo/bar') :Chained('/')> would bind to
-C</foo/bar/...>. If you don't specify C<:PathPart> it has the same
-effect as using C<:PathPart>, it would default to the action name.
-
-=item Chained
-
-Has to be specified for every child in the chain. Possible values are
-absolute and relative private action paths, with the relatives pointing
-to the current controller, or a single slash C</> to tell Catalyst that
-this is the root of a chain. The attribute C<:Chained> without aguments
-also defaults to the C</> behaviour.
-
-Due to the fact that you can specify an absolute path to the parent
-action, it doesn't matter to Catalyst where that parent is located. So,
-if your design requests it, you can redispatch a chain through every
-controller or namespace you want.
-
-Another interesting possibility gives C<:Chained('.')>, which chains
-itself to an action with the path of the current controllers namespace.
-For example:
-
-  #   in MyApp::Controller::Foo
-  sub bar : Chained CaptureArgs(1) { ... }
-
-  #   in MyApp::Controller::Foo::Bar
-  sub baz : Chained('.') Args(1) { ... }
-
-This builds up a chain like C</bar/*/baz/*>. The specification of C<.>
-as argument to Chained here chains the C<baz> action to an action with
-the path of the current controller namespace, namely C</foo/bar>. That
-action chains directly to C</>, so the above chain comes out as end
-product.
-
-=item CaptureArgs
-
-Also has to be specified for every part of the chain that is not an
-endpoint. With this attribute Catalyst knows how many of the following
-parts of the path (separated by C</>) this action wants to captures as
-it's arguments. If it doesn't expect any, just specify C<:CaptureArgs(0)>.
-The captures get passed to the action's C<@_> right after the context,
-but you can also find them as array reference in 
-C<$c-E<gt>request-E<gt>captures-E<gt>[$level]>. The C<$level> is the
-level of the action in the chain that captured the parts of the path.
-
-An action that is part of a chain (read: that has a C<:Chained> attribute)
-but has no C<:CaptureArgs> attribute is treated by Catalyst as a chain end.
-
-=item Args
-
-By default, endpoints receive the rest of the arguments in the path. You
-can tell Catalyst through C<:Args> explicitly how many arguments your
-endpoint expects, just like you can with C<:CaptureArgs>. Note that this
-also influences if this chain is invoked on a request. A chain with an
-endpoint specifying one argument will only match if exactly one argument
-exists in the path.
-
-You can specify an exact number of arguments like C<:Args(3)>, including
-C<0>. If you just say C<:Args> without any arguments, it is the same as
-leaving it out alltogether: The chain is matched independent of the number
-of path parts after the endpoint.
-
-Just like with C<:CaptureArgs>, the arguments get passed to the action in
-C<@_> after the context object. They can also be reached through
-C<$c-E<gt>request-E<gt>arguments>.
-
-=back
-
-Note that the list of C<auto> actions called depends on the private path
-of the endpoint of the chain, not on the chained actions way. The C<auto>
-actions will be run before the chain dispatching begins. In every other
-aspect, C<auto> actions behave as documented.
-
-The C<forward>ing to other actions does just what you would expect. But if
-you C<detach> out of a chain, the rest of the chain will not get called
-after the C<detach> returned.
-
 =item * B<Top-level> (B<Global>)
 
     package MyApp::Controller::Foo;
@@ -687,6 +444,24 @@ Catalyst ("MyApp::Controller" in the above example), replaces "::" with
 explanation of the pre-defined meaning of Catalyst component class
 names.
 
+=item * B<Chained>
+
+Catalyst also provides a method to build and dispatch chains of actions,
+like
+
+    sub foo : Chained : CaptureArgs(1) {
+        my ( $self, $c, $arg ) = @_;
+        ...
+    }
+
+    sub bar : Chained('foo') : Args(1) {
+        my ( $self, $c, $arg ) = @_;
+        ...
+    }
+
+to handle a C</foo/*/bar/*> path. For more information about this dispatch
+type, please read L<Catalyst::DispatchType::Chained>.
+
 =item * B<Private>
 
     sub foo : Private { }
index ee50214..a92f32c 100644 (file)
@@ -697,7 +697,10 @@ sub run_tests {
     #
     {
         eval { require 'TestAppChainedRecursive.pm' };
-        pass( "Interception of recursive chains" );
+        if ($run_number == 1) {
+            ok( ! $@, "Interception of recursive chains" );
+        }
+        else { pass( "Interception of recursive chains already tested" ) }
     }
 
     #