Args() wasn't being processed as unlimited number of args, due to
[catagits/Catalyst-Runtime.git] / lib / Catalyst / RouteMatching.pod
index e91b560..54dc51d 100644 (file)
@@ -10,6 +10,73 @@ This is a WIP document intended to help people understand the logic that L<Catal
 uses to determine how to match in incoming request to an action (or action chain)
 in a controller.
 
+=head2 Request to Controller/Action Matching
+
+L<Catalyst> maps requests to action using a 'longest path wins' approach.  That means
+that if the request is '/foo/bar/baz' That means the action 'baz' matches:
+
+    package MyApp::Controller::Foo;
+
+    use Moose;
+    use MooseX::MethodAttributes
+
+    extends 'Catalyst::Controller';
+
+    sub bar :Path('bar') Args(1) { ...}
+    sub baz :Path('bar/baz') Args(0) { ... }
+
+Path length matches take precidence over all other types of matches (included HTTP
+Method, Scheme, etc.).  The same holds true for Chained actions.  Generally the
+chain that matches the most PathParts wins.
+
+=head2 Args(N) versus Args
+
+'Args' matches any number of args.  Because this functions as a sort of catchall, we
+treat 'Args' as the lowest precedence of any Args(N) when N is 0 to infinity.  An
+action with 'Args' always get the last chance to match.
+
+=head2 When two or more actions match a given Path
+
+Sometimes two or more actions match the same path and all have the same pathpart
+length.  For example:
+
+    package MyApp::Controller::Root;
+
+    use Moose;
+    use MooseX::MethodAttributes
+
+    extends 'Catalyst::Controller';
+
+    sub root :Chained(/) CaptureArgs(0) { }
+
+      sub one :Chained(root) PathPart('') Args(0) { }
+      sub two :Chained(root) PathPart('') Args(0) { }
+      sub three :Chained(root) PathPart('') Args(0) { }
+
+    __PACKAGE__->meta->make_immutable;
+
+In this case the last defined action wins (for the example that is action 'three').
+
+This is most common to happen when you are using action matching beyond paths, such as
+when using method matching:
+
+    package MyApp::Controller::Root;
+
+    use Moose;
+    use MooseX::MethodAttributes
+
+    extends 'Catalyst::Controller';
+
+    sub root :Chained(/) CaptureArgs(0) { }
+
+      sub any :Chained(root) PathPart('') Args(0) { }
+      sub get :GET Chained(root) PathPart('') Args(0) { }
+
+    __PACKAGE__->meta->make_immutable;
+
+In the above example GET /root could match both actions.  In this case you should define
+your 'catchall' actions higher in the controller.
+
 =head2 Type Constraints in Args and Capture Args
 
 Beginning in Version 5.90090+ you may use L<Moose>, L<MooseX::Types> or L<Type::Tiny>
@@ -20,10 +87,11 @@ is a simple example:
 
     use Moose;
     use MooseX::MethodAttributes;
+    use MooseX::Types::Moose qw(Int);
 
     extends 'Catalyst::Controller';
 
-    sub find :Path('') Args('Int') {
+    sub find :Path('') Args(Int) {
       my ($self, $c, $int) = @_;
     }
 
@@ -84,6 +152,115 @@ A tutorial on how to make custom type libraries is outside the scope of this doc
 recommend looking at the copious documentation in L<Type::Tiny> or in L<MooseX::Types> if
 you prefer that system.  The author recommends L<Type::Tiny> if you are unsure which to use.
 
+=head3 Type constraint namespace.
+
+By default we assume the namespace which defines the type constraint is in the package
+which contains the action declaring the arg or capture arg.  However if you do not wish
+to import type constraints into you package, you may use a fully qualified namespace for
+your type constraint.  If you do this you must install L<Type::Tiny> which defines the
+code used to lookup and normalize the various types of Type constraint libraries.
+
+Example:
+
+    package MyApp::Example;
+
+    use Moose;
+    use MooseX::MethodAttributes;
+
+    extends 'Catalyst::Controller';
+
+    sub an_int_ns :Local Args(MyApp::Types::Int) {
+      my ($self, $c, $int) = @_;
+      $c->res->body('an_int (withrole)');
+    }
+
+Would basically work the same as:
+
+    package MyApp::Example;
+
+    use Moose;
+    use MooseX::MethodAttributes;
+    use MyApp::Types 'Int';
+
+    extends 'Catalyst::Controller';
+
+    sub an_int_ns :Local Args(Int) {
+      my ($self, $c, $int) = @_;
+      $c->res->body('an_int (withrole)');
+    }
+
+=head3 namespace::autoclean
+
+If you want to use L<namespace::autoclean> in your controllers you must 'except' imported
+type constraints since the code that resolves type constraints in args / capture args
+run after the cleaning.  For example:
+
+    package MyApp::Controller::Autoclean;
+
+    use Moose;
+    use MooseX::MethodAttributes;
+    use namespace::autoclean -except => 'Int';
+    use MyApp::Types qw/Int/;
+
+    extends 'Catalyst::Controller';
+
+    sub an_int :Local Args(Int) {
+      my ($self, $c, $int) = @_;
+      $c->res->body('an_int (autoclean)');
+    }
+
+=head3 Using roles and base controller with type constraints
+
+If your controller is using a base class or a role that has an action with a type constraint
+you should declare your use of the type constraint in that role or base controller in the
+same way as you do in main controllers.  Catalyst will try to find the package with declares
+the type constraint first by looking in any roles and then in superclasses.  It will use the
+first package that defines the type constraint.  For example:
+
+    package MyApp::Role;
+
+    use Moose::Role;
+    use MooseX::MethodAttributes::Role;
+    use MyApp::Types qw/Int/;
+
+    sub an_int :Local Args(Int) {
+      my ($self, $c, $int) = @_;
+      $c->res->body('an_int (withrole)');
+    }
+
+    sub an_int_ns :Local Args(MyApp::Types::Int) {
+      my ($self, $c, $int) = @_;
+      $c->res->body('an_int (withrole)');
+    }
+
+    package MyApp::BaseController;
+
+    use Moose;
+    use MooseX::MethodAttributes;
+    use MyApp::Types qw/Int/;
+
+    extends 'Catalyst::Controller';
+
+    sub from_parent :Local Args(Int) {
+      my ($self, $c, $id) = @_;
+      $c->res->body('from_parent $id');
+    }
+
+    package MyApp::Controller::WithRole;
+
+    use Moose;
+    use MooseX::MethodAttributes;
+
+    extends 'MyApp::BaseController';
+
+    with 'MyApp::Role';
+
+If you have complex controller hierarchy, we
+do not at this time attempt to look for all packages with a match type constraint, but instead
+take the first one found.  In the future we may add code that attempts to insure a sane use
+of subclasses with type constraints but right now there are no clear use cases so report issues
+and interests.
+
 =head3 Match order when more than one Action matches a path.
 
 As previously described, L<Catalyst> will match 'the longest path', which generally means
@@ -208,10 +385,6 @@ parent controller or consumed role.
 
 Please note that your declared type constraint names will now appear in the debug console.
 
-=head1 Conclusion
-
-    TBD
-
 =head1 Author
 
 John Napiorkowski L<jjnapiork@cpan.org|email:jjnapiork@cpan.org>