| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Catalyst::ControllerRole::At; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 1 |  |  | 1 |  | 2450 | use Moose::Role; | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 8 |  | 
| 4 |  |  |  |  |  |  | our $VERSION = '0.008'; | 
| 5 |  |  |  |  |  |  |  | 
| 6 |  |  |  |  |  |  | sub _parse_Get_attr { | 
| 7 | 1 |  |  | 1 |  | 5530 | my ($self, $app, $action_subname, $value) = @_; | 
| 8 | 1 |  |  |  |  | 4 | my %attributes = $self->_parse_At_attr($app, $action_subname, $value); | 
| 9 | 1 |  |  |  |  | 5 | $attributes{Method} = 'GET'; | 
| 10 | 1 |  |  |  |  | 6 | return %attributes; | 
| 11 |  |  |  |  |  |  | } | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | sub _parse_Post_attr { | 
| 14 | 1 |  |  | 1 |  | 39039 | my ($self, $app, $action_subname, $value) = @_; | 
| 15 | 1 |  |  |  |  | 5 | my %attributes = $self->_parse_At_attr($app, $action_subname, $value); | 
| 16 | 1 |  |  |  |  | 7 | $attributes{Method} = 'POST'; | 
| 17 | 1 |  |  |  |  | 7 | return %attributes; | 
| 18 |  |  |  |  |  |  | } | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | sub _parse_Put_attr { | 
| 21 | 1 |  |  | 1 |  | 5588 | my ($self, $app, $action_subname, $value) = @_; | 
| 22 | 1 |  |  |  |  | 4 | my %attributes = $self->_parse_At_attr($app, $action_subname, $value); | 
| 23 | 1 |  |  |  |  | 4 | $attributes{Method} = 'PUT'; | 
| 24 | 1 |  |  |  |  | 6 | return %attributes; | 
| 25 |  |  |  |  |  |  | } | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | sub _parse_Delete_attr { | 
| 28 | 0 |  |  | 0 |  | 0 | my ($self, $app, $action_subname, $value) = @_; | 
| 29 | 0 |  |  |  |  | 0 | my %attributes = $self->_parse_At_attr($app, $action_subname, $value); | 
| 30 | 0 |  |  |  |  | 0 | $attributes{Method} = 'DELETE'; | 
| 31 | 0 |  |  |  |  | 0 | return %attributes; | 
| 32 |  |  |  |  |  |  | } | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | sub _parse_Head_attr { | 
| 35 | 0 |  |  | 0 |  | 0 | my ($self, $app, $action_subname, $value) = @_; | 
| 36 | 0 |  |  |  |  | 0 | my %attributes = $self->_parse_At_attr($app, $action_subname, $value); | 
| 37 | 0 |  |  |  |  | 0 | $attributes{Method} = 'HEAD'; | 
| 38 | 0 |  |  |  |  | 0 | return %attributes; | 
| 39 |  |  |  |  |  |  | } | 
| 40 |  |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  | sub _parse_Options_attr { | 
| 42 | 0 |  |  | 0 |  | 0 | my ($self, $app, $action_subname, $value) = @_; | 
| 43 | 0 |  |  |  |  | 0 | my %attributes = $self->_parse_At_attr($app, $action_subname, $value); | 
| 44 | 0 |  |  |  |  | 0 | $attributes{Method} = 'OPTIONS'; | 
| 45 | 0 |  |  |  |  | 0 | return %attributes; | 
| 46 |  |  |  |  |  |  | } | 
| 47 |  |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  | sub _parse_Patch_attr { | 
| 49 | 0 |  |  | 0 |  | 0 | my ($self, $app, $action_subname, $value) = @_; | 
| 50 | 0 |  |  |  |  | 0 | my %attributes = $self->_parse_At_attr($app, $action_subname, $value); | 
| 51 | 0 |  |  |  |  | 0 | $attributes{Method} = 'PATCH'; | 
| 52 | 0 |  |  |  |  | 0 | return %attributes; | 
| 53 |  |  |  |  |  |  | } | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | sub _parse_At_attr { | 
| 56 | 20 |  |  | 20 |  | 684681 | my ($self, $app, $action_subname, $value) = @_; | 
| 57 | 20 |  |  |  |  | 64 | my ($chained, $path_part, $arg_type, $args, %extra_proto) = ('/','','Args',0, ()); | 
| 58 |  |  |  |  |  |  |  | 
| 59 | 20 |  |  |  |  | 88 | my @controller_path_parts = split('/', $self->path_prefix($app)); | 
| 60 | 20 |  |  |  |  | 2275 | my @parent_controller_path_parts = @controller_path_parts; | 
| 61 | 20 |  |  |  |  | 43 | my $affix = pop @parent_controller_path_parts; | 
| 62 |  |  |  |  |  |  |  | 
| 63 | 20 |  | 100 |  |  | 218 | my %expansions = ( | 
| 64 |  |  |  |  |  |  | '$up' => '/' . join('/', @parent_controller_path_parts), | 
| 65 |  |  |  |  |  |  | '$parent' =>  '/' . join('/', @parent_controller_path_parts, $action_subname), | 
| 66 |  |  |  |  |  |  | '$name' => $action_subname, | 
| 67 |  |  |  |  |  |  | '$controller' => '/' . join('/', @controller_path_parts), | 
| 68 |  |  |  |  |  |  | '$action' => '/' . join('/', @controller_path_parts, $action_subname), | 
| 69 |  |  |  |  |  |  | '$affix' =>  '/' . ($affix||''), | 
| 70 |  |  |  |  |  |  | ); | 
| 71 |  |  |  |  |  |  |  | 
| 72 | 20 |  |  |  |  | 55 | $expansions{'$path_prefix'} = $expansions{'$controller'}; # Backwards compatibility | 
| 73 | 20 |  |  |  |  | 45 | $expansions{'$path_end'} = $expansions{'$affix'}; # Backwards compatibility | 
| 74 |  |  |  |  |  |  |  | 
| 75 | 20 |  | 100 |  |  | 66 | $value = ($value||'') . ''; | 
| 76 | 20 |  |  |  |  | 109 | my ($path, $query) = ($value=~/^([^?]*)\??(.*)$/); | 
| 77 | 20 | 100 | 100 |  |  | 92 | my (@path_parts) = map { $expansions{$_} ? $expansions{$_} :$_ } split('/', ($path||'')); | 
|  | 41 |  |  |  |  | 136 |  | 
| 78 |  |  |  |  |  |  |  | 
| 79 | 20 |  |  |  |  | 43 | my @arg_proto; | 
| 80 |  |  |  |  |  |  | my @named_fields; | 
| 81 |  |  |  |  |  |  |  | 
| 82 | 20 | 100 |  |  |  | 48 | if($query) { | 
| 83 | 1 |  |  |  |  | 10 | my @q = ($query=~m/{(.+?)}/g); | 
| 84 | 1 |  |  |  |  | 5 | $extra_proto{QueryParam} = \@q; | 
| 85 | 1 |  |  |  |  | 3 | foreach my $q (@q) { | 
| 86 | 2 |  |  |  |  | 9 | my ($q_part, $type) = split(':', $q); | 
| 87 | 2 | 50 |  |  |  | 8 | if(defined($q_part)) { | 
| 88 | 2 | 100 |  |  |  | 7 | if($q_part=~m/=/) { | 
| 89 | 1 |  |  |  |  | 4 | ($q_part) = split('=', $q_part); # Discard any=default | 
| 90 |  |  |  |  |  |  | } | 
| 91 | 2 |  |  |  |  | 6 | $q_part=~s/^[!?]//; | 
| 92 |  |  |  |  |  |  | $extra_proto{Field} = $extra_proto{Field} ? | 
| 93 | 2 | 100 |  |  |  | 17 | "$extra_proto{Field},$q_part=>\$query{$q_part}" : "$q_part=>\$query{$q_part}" | 
| 94 |  |  |  |  |  |  | } | 
| 95 |  |  |  |  |  |  | } | 
| 96 |  |  |  |  |  |  | } | 
| 97 |  |  |  |  |  |  |  | 
| 98 | 20 | 100 | 100 |  |  | 65 | if(($path_parts[-1]||'') eq '...') { | 
| 99 | 3 |  |  |  |  | 6 | $arg_type = 'CaptureArgs'; | 
| 100 | 3 |  |  |  |  | 6 | pop @path_parts; | 
| 101 |  |  |  |  |  |  | } | 
| 102 |  |  |  |  |  |  |  | 
| 103 | 20 |  | 100 |  |  | 111 | while(my ($spec) = (($path_parts[-1]||'') =~m/^{(.*)}$/)) { | 
| 104 | 17 | 100 |  |  |  | 70 | if($spec) { | 
| 105 | 15 |  |  |  |  | 42 | my ($name, $constraint) = split(':', $spec); | 
| 106 | 15 | 100 |  |  |  | 45 | unshift @arg_proto, $constraint if $constraint; | 
| 107 | 15 | 100 |  |  |  | 33 | if($name) { | 
| 108 | 11 | 100 |  |  |  | 29 | if($name eq '*') { | 
| 109 | 2 |  |  |  |  | 5 | $args = undef; | 
| 110 |  |  |  |  |  |  | } else { | 
| 111 | 9 |  |  |  |  | 20 | unshift @named_fields, $name; | 
| 112 |  |  |  |  |  |  | } | 
| 113 |  |  |  |  |  |  | } else { | 
| 114 | 4 |  |  |  |  | 8 | unshift @named_fields, undef; | 
| 115 |  |  |  |  |  |  | } | 
| 116 |  |  |  |  |  |  | } | 
| 117 | 17 | 100 |  |  |  | 38 | $args++ if defined $args; | 
| 118 |  |  |  |  |  |  | } continue { | 
| 119 | 17 |  |  |  |  | 81 | pop @path_parts; | 
| 120 |  |  |  |  |  |  | } | 
| 121 |  |  |  |  |  |  |  | 
| 122 |  |  |  |  |  |  | { | 
| 123 | 20 |  |  |  |  | 31 | my $cnt = 0; | 
|  | 20 |  |  |  |  | 38 |  | 
| 124 | 20 |  |  |  |  | 42 | foreach my $name (@named_fields) { | 
| 125 | 13 | 100 |  |  |  | 58 | if(defined($name)) { | 
| 126 |  |  |  |  |  |  | $extra_proto{Field} = $extra_proto{Field} ? | 
| 127 | 9 | 100 |  |  |  | 43 | "$extra_proto{Field},$name=>\$args[$cnt]" : "$name=>\$args[$cnt]" | 
| 128 |  |  |  |  |  |  | } | 
| 129 | 13 |  |  |  |  | 42 | $cnt++; | 
| 130 |  |  |  |  |  |  | } | 
| 131 |  |  |  |  |  |  | } | 
| 132 |  |  |  |  |  |  |  | 
| 133 | 20 | 100 |  |  |  | 33 | if( | 
| 134 | 10 |  |  |  |  | 97 | my ($key, $value) = map { $_ =~ /^(.*?)(?:\(\s*['"]?(.+?)['"]?\s*\))?$/ } grep { $_ =~m/^Via\(.+\)$/ } | 
|  | 30 |  |  |  |  | 1892 |  | 
| 135 | 20 | 50 |  |  |  | 87 | @{$self->meta->get_method($action_subname)->attributes||[]}) | 
| 136 |  |  |  |  |  |  | { | 
| 137 | 10 | 100 |  |  |  | 33 | $chained = join '/', grep { defined $_  } map { $expansions{$_} ? $expansions{$_} : $_ } split('\/',$value); | 
|  | 12 |  |  |  |  | 42 |  | 
|  | 12 |  |  |  |  | 50 |  | 
| 138 | 10 |  |  |  |  | 30 | $chained =~s[//][/]g; | 
| 139 |  |  |  |  |  |  | } | 
| 140 |  |  |  |  |  |  |  | 
| 141 | 20 |  |  |  |  | 54 | $path_part = join('/', @path_parts); | 
| 142 | 20 |  |  |  |  | 70 | $path_part =~s/^\///; | 
| 143 |  |  |  |  |  |  |  | 
| 144 | 20 | 100 |  |  |  | 146 | my %attributes = ( | 
| 145 |  |  |  |  |  |  | Chained   => $chained, | 
| 146 |  |  |  |  |  |  | PathPart  => $path_part, | 
| 147 |  |  |  |  |  |  | Does => [qw/NamedFields QueryParameter/], | 
| 148 |  |  |  |  |  |  | $arg_type => (@arg_proto ? (join(',',@arg_proto)) : $args), | 
| 149 |  |  |  |  |  |  | %extra_proto, | 
| 150 |  |  |  |  |  |  | ); | 
| 151 |  |  |  |  |  |  |  | 
| 152 | 20 |  |  |  |  | 181 | return %attributes; | 
| 153 |  |  |  |  |  |  | } | 
| 154 |  |  |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | 1; | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | =head1 NAME | 
| 158 |  |  |  |  |  |  |  | 
| 159 |  |  |  |  |  |  | Catalyst::ControllerRole::At - A new approach to building Catalyst actions | 
| 160 |  |  |  |  |  |  |  | 
| 161 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  | package MyApp::Controller::User; | 
| 164 |  |  |  |  |  |  |  | 
| 165 |  |  |  |  |  |  | use Moose; | 
| 166 |  |  |  |  |  |  | use MooseX::MethodAttributes; | 
| 167 |  |  |  |  |  |  | use Types::Standard qw/Int Str/; | 
| 168 |  |  |  |  |  |  |  | 
| 169 |  |  |  |  |  |  | extends 'Catalyst::Controller'; | 
| 170 |  |  |  |  |  |  | with 'Catalyst::ControllerRole::At'; | 
| 171 |  |  |  |  |  |  |  | 
| 172 |  |  |  |  |  |  | # Define your actions, for example: | 
| 173 |  |  |  |  |  |  |  | 
| 174 |  |  |  |  |  |  | sub global :At(/global/{}/{}) { ... }         # http://localhost/global/$arg/$arg | 
| 175 |  |  |  |  |  |  |  | 
| 176 |  |  |  |  |  |  | sub list   :At($action?{q:Str}) { ... }       # http://localhost/user/list?q=$string | 
| 177 |  |  |  |  |  |  |  | 
| 178 |  |  |  |  |  |  | sub find   :At($controller/{id:Int}) { ... }  # http://localhost/user/$integer | 
| 179 |  |  |  |  |  |  |  | 
| 180 |  |  |  |  |  |  | # Define an action with an HTTP Method match at the same time | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | sub update :Get($controller/{id:Int}) { ... } # GET http://localhost/user/$integer | 
| 183 |  |  |  |  |  |  |  | 
| 184 |  |  |  |  |  |  | __PACKAGE__->meta->make_immutable; | 
| 185 |  |  |  |  |  |  |  | 
| 186 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 187 |  |  |  |  |  |  |  | 
| 188 |  |  |  |  |  |  | The way L<Catalyst> uses method attributes to annote a subroutine with meta | 
| 189 |  |  |  |  |  |  | information used to map that action to an incoming request has sometimes been difficult | 
| 190 |  |  |  |  |  |  | for newcomers to the framework.  Partly this is due to how the system evolved and was | 
| 191 |  |  |  |  |  |  | augmented, with more care towards backwards compatibility (for example with L<Maypole>, its | 
| 192 |  |  |  |  |  |  | architectural anscestor) than with designing a forward system that is easy to grasp. | 
| 193 |  |  |  |  |  |  | Additionally aspects of the system such as chained dispatch are very useful in the | 
| 194 |  |  |  |  |  |  | hands of an expert but the interface leaves a lot to be desired.  For example it is | 
| 195 |  |  |  |  |  |  | possible to craft actions that mix chaining syntax with 'classic' syntax in ways that | 
| 196 |  |  |  |  |  |  | are confusing.  And having more than one way to do the same thing without clear and | 
| 197 |  |  |  |  |  |  | obvious benefits is confusing to newcomers. | 
| 198 |  |  |  |  |  |  |  | 
| 199 |  |  |  |  |  |  | Lastly, the core L<Catalyst::Controller> syntax has confusing defaults that are not readily guessed. | 
| 200 |  |  |  |  |  |  | For example do you know the difference (if any) between Args and Args()?  Or the difference | 
| 201 |  |  |  |  |  |  | between Path, Path(''), and Path()?  In many cases defaults are applied that were not | 
| 202 |  |  |  |  |  |  | intended and things that you might think are the same turn out to have different effects.  All | 
| 203 |  |  |  |  |  |  | this conspires to worsen the learning curve. | 
| 204 |  |  |  |  |  |  |  | 
| 205 |  |  |  |  |  |  | This role defines an alternative syntax that we hope is easier to understand and for the most | 
| 206 |  |  |  |  |  |  | part eliminates defaults and guessed intentions.  It only defines two method attributes, "At()" | 
| 207 |  |  |  |  |  |  | and "Via()", which have no defaults and one of which is always required.  It also smooths | 
| 208 |  |  |  |  |  |  | over differences between 'classic' route matching using :Local and :Path and the newer | 
| 209 |  |  |  |  |  |  | syntax based on Chaining by providing a single approach that bridges between the two | 
| 210 |  |  |  |  |  |  | styles.  One can mix and match the two without being required to learn a new syntax or to | 
| 211 |  |  |  |  |  |  | rearchitect the system. | 
| 212 |  |  |  |  |  |  |  | 
| 213 |  |  |  |  |  |  | The "At()" syntax more closely resembles the type of URL you are trying to match, which should | 
| 214 |  |  |  |  |  |  | make code creation and maintainance easier by reducing the mental mismatch that happens with | 
| 215 |  |  |  |  |  |  | the core syntax. | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | Ultimately this ControllerRole is an attempt to layer some sugar on top of the existing | 
| 218 |  |  |  |  |  |  | interface with the hope to establishing a normalized, easy approach that doesn't have the | 
| 219 |  |  |  |  |  |  | learning curve or confusion of the existing system. | 
| 220 |  |  |  |  |  |  |  | 
| 221 |  |  |  |  |  |  | I also recommend reading L<Catalyst::RouteMatching> for general notes and details on | 
| 222 |  |  |  |  |  |  | how dispatching and matching works. | 
| 223 |  |  |  |  |  |  |  | 
| 224 |  |  |  |  |  |  | =head1 URL Templating | 
| 225 |  |  |  |  |  |  |  | 
| 226 |  |  |  |  |  |  | The following are examples and specification for how to map a URL to an action or to | 
| 227 |  |  |  |  |  |  | a chain of actions in L<Catalyst>. All examples assume the application is running at | 
| 228 |  |  |  |  |  |  | the root of your website domain (https://localhost/, not https://localhost/somepath) | 
| 229 |  |  |  |  |  |  |  | 
| 230 |  |  |  |  |  |  | =head2 Matching a Literal Path | 
| 231 |  |  |  |  |  |  |  | 
| 232 |  |  |  |  |  |  | The action 'global_path' will respond to 'https://localhost/foo/bar/baz'. | 
| 233 |  |  |  |  |  |  |  | 
| 234 |  |  |  |  |  |  | package MyApp::Controller::Example; | 
| 235 |  |  |  |  |  |  |  | 
| 236 |  |  |  |  |  |  | use Moose; | 
| 237 |  |  |  |  |  |  | use MooseX::MethodAttributes; | 
| 238 |  |  |  |  |  |  |  | 
| 239 |  |  |  |  |  |  | extends 'Catalyst::Controller'; | 
| 240 |  |  |  |  |  |  | with 'Catalyst::ControllerRole::At'; | 
| 241 |  |  |  |  |  |  |  | 
| 242 |  |  |  |  |  |  | sub global_path :At(/foo/bar/baz) { ... } | 
| 243 |  |  |  |  |  |  |  | 
| 244 |  |  |  |  |  |  | __PACKAGE__->meta->make_immutable; | 
| 245 |  |  |  |  |  |  |  | 
| 246 |  |  |  |  |  |  | The main two parts are consuming the role c< with 'Catalyst::ControllerRole::At'> | 
| 247 |  |  |  |  |  |  | and using the C<At> method attribute.  This attribute can only appear once in your | 
| 248 |  |  |  |  |  |  | action and should be string that matches a specification as to be described in the | 
| 249 |  |  |  |  |  |  | following examples. | 
| 250 |  |  |  |  |  |  |  | 
| 251 |  |  |  |  |  |  | =head2 Arguments in a Path specification | 
| 252 |  |  |  |  |  |  |  | 
| 253 |  |  |  |  |  |  | Often you wish to parameterize your URL template such that instead of matching a full | 
| 254 |  |  |  |  |  |  | literal path, you may instead place slots for placeholders, which get passed to the | 
| 255 |  |  |  |  |  |  | action during a request.  For example: | 
| 256 |  |  |  |  |  |  |  | 
| 257 |  |  |  |  |  |  | package MyApp::Controller::Example; | 
| 258 |  |  |  |  |  |  |  | 
| 259 |  |  |  |  |  |  | use Moose; | 
| 260 |  |  |  |  |  |  | use MooseX::MethodAttributes; | 
| 261 |  |  |  |  |  |  |  | 
| 262 |  |  |  |  |  |  | extends 'Catalyst::Controller'; | 
| 263 |  |  |  |  |  |  | with 'Catalyst::ControllerRole::At'; | 
| 264 |  |  |  |  |  |  |  | 
| 265 |  |  |  |  |  |  | sub args :At(/example/{}) { | 
| 266 |  |  |  |  |  |  | my ($self, $c, $arg) = @_; | 
| 267 |  |  |  |  |  |  | } | 
| 268 |  |  |  |  |  |  |  | 
| 269 |  |  |  |  |  |  | __PACKAGE__->meta->make_immutable; | 
| 270 |  |  |  |  |  |  |  | 
| 271 |  |  |  |  |  |  | In the above controller we'd match a URL like 'https://localhost/example/100' and | 
| 272 |  |  |  |  |  |  | 'https://localhost/example/whatever'.  The parameterized argument is passed as '$arg' | 
| 273 |  |  |  |  |  |  | into the action when a request is matched. | 
| 274 |  |  |  |  |  |  |  | 
| 275 |  |  |  |  |  |  | You may have as many argument placeholders as you wish, or you may specific an open | 
| 276 |  |  |  |  |  |  | ended number of placeholders: | 
| 277 |  |  |  |  |  |  |  | 
| 278 |  |  |  |  |  |  | sub arg2 :At(/example/{}/{}) { ... }  # https://localhost/example/foo/bar | 
| 279 |  |  |  |  |  |  | sub args :At(/example/{*} { ... }     # https://localhost/example/1/2/3/4/... | 
| 280 |  |  |  |  |  |  |  | 
| 281 |  |  |  |  |  |  | In this case action 'arg2' matches its path with 2 arguments, while 'args' will match | 
| 282 |  |  |  |  |  |  | 'any number of arguments', subject to operating system limitations. | 
| 283 |  |  |  |  |  |  |  | 
| 284 |  |  |  |  |  |  | B<NOTE> Since the open ended argument specification can catch lots of URLs, this type | 
| 285 |  |  |  |  |  |  | of argument specification is run as a special 'low priorty' match.  For example (using | 
| 286 |  |  |  |  |  |  | the above two actions) should the request be 'https://localhost/example/foo/bar', then | 
| 287 |  |  |  |  |  |  | the first action 'arg2' would match since its a better match for that request given it | 
| 288 |  |  |  |  |  |  | has a more constrained specification. In general I recommend using '{*}' sparingly. | 
| 289 |  |  |  |  |  |  |  | 
| 290 |  |  |  |  |  |  | B<NOTE> Placeholder must come after path part literals or expansion variables as discussed | 
| 291 |  |  |  |  |  |  | below.  For example "At(/bar/{}/bar)" is not valid.  This type of match is possible with | 
| 292 |  |  |  |  |  |  | chained actions (see more examples below). | 
| 293 |  |  |  |  |  |  |  | 
| 294 |  |  |  |  |  |  | =head2 Naming your Arguments | 
| 295 |  |  |  |  |  |  |  | 
| 296 |  |  |  |  |  |  | You may name your argument placeholders.  If you do so you can access your argument | 
| 297 |  |  |  |  |  |  | placeholder values via the %_ hash.  For example: | 
| 298 |  |  |  |  |  |  |  | 
| 299 |  |  |  |  |  |  | sub args :At(/example/{id}) { | 
| 300 |  |  |  |  |  |  | my ($self, $c, $id) = @_; | 
| 301 |  |  |  |  |  |  | $c->response->body("The requested ID is $_{id}"); | 
| 302 |  |  |  |  |  |  | } | 
| 303 |  |  |  |  |  |  |  | 
| 304 |  |  |  |  |  |  | Note that regardless of whether you name your arguments or not, they will get passed to | 
| 305 |  |  |  |  |  |  | your actions at request via @_, as in core L<Catalyst>.  So in the above example '$id' | 
| 306 |  |  |  |  |  |  | is equal to '$_{id}'.  You may use whichever makes the most sense for your task, or | 
| 307 |  |  |  |  |  |  | standardize a project on one form or the other.  You might also find naming the arguments | 
| 308 |  |  |  |  |  |  | to be a useful form of documentation. | 
| 309 |  |  |  |  |  |  |  | 
| 310 |  |  |  |  |  |  | =head2 Type constraints on your Arguments | 
| 311 |  |  |  |  |  |  |  | 
| 312 |  |  |  |  |  |  | You may leverage the built in support for applying type constraints on your arguments: | 
| 313 |  |  |  |  |  |  |  | 
| 314 |  |  |  |  |  |  | package MyApp::Controller::Example; | 
| 315 |  |  |  |  |  |  |  | 
| 316 |  |  |  |  |  |  | use Moose; | 
| 317 |  |  |  |  |  |  | use MooseX::MethodAttributes; | 
| 318 |  |  |  |  |  |  | use Types::Standard qw/Int/; | 
| 319 |  |  |  |  |  |  |  | 
| 320 |  |  |  |  |  |  | extends 'Catalyst::Controller'; | 
| 321 |  |  |  |  |  |  | with 'Catalyst::ControllerRole::At'; | 
| 322 |  |  |  |  |  |  |  | 
| 323 |  |  |  |  |  |  | sub args :At(/example/{id:Int}) { | 
| 324 |  |  |  |  |  |  | my ($self, $c, $id) = @_; | 
| 325 |  |  |  |  |  |  | } | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | __PACKAGE__->meta->make_immutable; | 
| 328 |  |  |  |  |  |  |  | 
| 329 |  |  |  |  |  |  | Would match 'http://localhost/example/100' but not 'http://localhost/example/string' | 
| 330 |  |  |  |  |  |  |  | 
| 331 |  |  |  |  |  |  | All the same rules that apply to L<Catalyst> regarding use of type constraints apply.  Most | 
| 332 |  |  |  |  |  |  | importantly you must remember to inport your type constraints, as in the above example.  You | 
| 333 |  |  |  |  |  |  | should consider reviewing L<Catalyst::RouteMatching> for more general help. | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | You may declare a type constraint on an argument but not name it, as in the following | 
| 336 |  |  |  |  |  |  | example: | 
| 337 |  |  |  |  |  |  |  | 
| 338 |  |  |  |  |  |  | sub args :At(/example/{:Int}) { | 
| 339 |  |  |  |  |  |  | my ($self, $c, $id) = @_; | 
| 340 |  |  |  |  |  |  | } | 
| 341 |  |  |  |  |  |  |  | 
| 342 |  |  |  |  |  |  | Note the ':' prepended to the type constraint name is NOT optional. | 
| 343 |  |  |  |  |  |  |  | 
| 344 |  |  |  |  |  |  | B<NOTE> Using type constraints in your route matching can have performance implications. | 
| 345 |  |  |  |  |  |  |  | 
| 346 |  |  |  |  |  |  | B<NOTE> If you have more than one argument placeholder and you apply a type constraint to | 
| 347 |  |  |  |  |  |  | one, you must apply constraints to all.  You may use an open type constraint like C<Any> | 
| 348 |  |  |  |  |  |  | as defined in L<Types::Standard> for placeholders where you don't care what the value is.  For | 
| 349 |  |  |  |  |  |  | example: | 
| 350 |  |  |  |  |  |  |  | 
| 351 |  |  |  |  |  |  | use Types::Standard qw/Any Int/; | 
| 352 |  |  |  |  |  |  |  | 
| 353 |  |  |  |  |  |  | sub args :At(/example/{:Any}/{:Int}) { | 
| 354 |  |  |  |  |  |  | my ($self, $c, $id) = @_; | 
| 355 |  |  |  |  |  |  | } | 
| 356 |  |  |  |  |  |  |  | 
| 357 |  |  |  |  |  |  | =head2 Expansion Variables in your Path | 
| 358 |  |  |  |  |  |  |  | 
| 359 |  |  |  |  |  |  | B<NOTE> Over the years since this role was first written I have found in general that | 
| 360 |  |  |  |  |  |  | these expansions seem to add more confusion then they are worth.  I find I really don't | 
| 361 |  |  |  |  |  |  | need them.  Your results may vary.  I won't remove them for back compat reasons, but | 
| 362 |  |  |  |  |  |  | I recommend using them sparingly.  '$affix' appears to have some value but the name isn't | 
| 363 |  |  |  |  |  |  | very good.  I Added an alias '$path_end' which is slightly better I think.   Recommendations | 
| 364 |  |  |  |  |  |  | welcomed. | 
| 365 |  |  |  |  |  |  |  | 
| 366 |  |  |  |  |  |  | Generally you would prefer not to hardcode the full path of your actions, as in the | 
| 367 |  |  |  |  |  |  | examples given so far.  General Catalyst best practice is to have your actions live | 
| 368 |  |  |  |  |  |  | under the namespace of the controller in which they are defined.  That makes things | 
| 369 |  |  |  |  |  |  | more organized and easier to find as your application grows in complexity.  In order | 
| 370 |  |  |  |  |  |  | to make this and other common action template patterns easier, we support the following | 
| 371 |  |  |  |  |  |  | variable expansions in your URL template specification: | 
| 372 |  |  |  |  |  |  |  | 
| 373 |  |  |  |  |  |  | $controller: Your controller namespace (as an absolute path) | 
| 374 |  |  |  |  |  |  | $path_prefix: Alias for $controller | 
| 375 |  |  |  |  |  |  | $action: The action namespace (same as $controller/$name) | 
| 376 |  |  |  |  |  |  | $up: The namespace of the controller containing this controller | 
| 377 |  |  |  |  |  |  | $name The name of your action (the subroutine name) | 
| 378 |  |  |  |  |  |  | $affix: The last part of the controller namespace. | 
| 379 |  |  |  |  |  |  |  | 
| 380 |  |  |  |  |  |  | For example if your controller is 'MyApp::Controller::User::Details' then: | 
| 381 |  |  |  |  |  |  |  | 
| 382 |  |  |  |  |  |  | $controller => /user/details | 
| 383 |  |  |  |  |  |  | $up => /user | 
| 384 |  |  |  |  |  |  | $affix => /details | 
| 385 |  |  |  |  |  |  |  | 
| 386 |  |  |  |  |  |  | And if 'MyApp::Controller::User::Details' contained an action like: | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  | sub list :At() { ... } | 
| 389 |  |  |  |  |  |  |  | 
| 390 |  |  |  |  |  |  | then: | 
| 391 |  |  |  |  |  |  |  | 
| 392 |  |  |  |  |  |  | $name => /list | 
| 393 |  |  |  |  |  |  | $action => /user/details/list | 
| 394 |  |  |  |  |  |  |  | 
| 395 |  |  |  |  |  |  | You use these variable expansions the same way as literal paths: | 
| 396 |  |  |  |  |  |  |  | 
| 397 |  |  |  |  |  |  | package MyApp::Controller::Example; | 
| 398 |  |  |  |  |  |  |  | 
| 399 |  |  |  |  |  |  | use Moose; | 
| 400 |  |  |  |  |  |  | use MooseX::MethodAttributes; | 
| 401 |  |  |  |  |  |  | use Types::Standard qw/Int/; | 
| 402 |  |  |  |  |  |  |  | 
| 403 |  |  |  |  |  |  | extends 'Catalyst::Controller'; | 
| 404 |  |  |  |  |  |  | with 'Catalyst::ControllerRole::At'; | 
| 405 |  |  |  |  |  |  |  | 
| 406 |  |  |  |  |  |  | sub args :At($controller/{id:Int}) { | 
| 407 |  |  |  |  |  |  | my ($self, $c, $id) = @_; | 
| 408 |  |  |  |  |  |  | } | 
| 409 |  |  |  |  |  |  |  | 
| 410 |  |  |  |  |  |  | sub list :At($action) { ... } | 
| 411 |  |  |  |  |  |  |  | 
| 412 |  |  |  |  |  |  | __PACKAGE__->meta->make_immutable; | 
| 413 |  |  |  |  |  |  |  | 
| 414 |  |  |  |  |  |  | In this example the action 'args' would match 'https://localhost/example/100' (with '100' being | 
| 415 |  |  |  |  |  |  | considered an argument) while action 'list' would match 'https::/localhost/example/list'. | 
| 416 |  |  |  |  |  |  |  | 
| 417 |  |  |  |  |  |  | You can use expansion variables in your base controllers or controller roles to more | 
| 418 |  |  |  |  |  |  | easily make shared actions. | 
| 419 |  |  |  |  |  |  |  | 
| 420 |  |  |  |  |  |  | B<NOTE> Your controller namespace is typically based on its package name, unless you | 
| 421 |  |  |  |  |  |  | have overridden it by setting an alternative in the configuation value 'namespace', or | 
| 422 |  |  |  |  |  |  | your have in some way overridden the logic that produces a namespace.  The default | 
| 423 |  |  |  |  |  |  | behavior is to produce a namespace like the following: | 
| 424 |  |  |  |  |  |  |  | 
| 425 |  |  |  |  |  |  | package MyApp::Controller::User => /user | 
| 426 |  |  |  |  |  |  | package MyApp::Controller::User::name => /user/name | 
| 427 |  |  |  |  |  |  |  | 
| 428 |  |  |  |  |  |  | Changing the way a controller defines its namespace will also change how actions that are | 
| 429 |  |  |  |  |  |  | defined in that controller defines thier namespaces. | 
| 430 |  |  |  |  |  |  |  | 
| 431 |  |  |  |  |  |  | B<NOTE> WHen using expansions, you should not place a '/' at the start of your | 
| 432 |  |  |  |  |  |  | template URI. | 
| 433 |  |  |  |  |  |  |  | 
| 434 |  |  |  |  |  |  | =head2 Matching GET parameters | 
| 435 |  |  |  |  |  |  |  | 
| 436 |  |  |  |  |  |  | You can match GET (query) parameters in your URL template definitions: | 
| 437 |  |  |  |  |  |  |  | 
| 438 |  |  |  |  |  |  | package MyApp::Controller::Example; | 
| 439 |  |  |  |  |  |  |  | 
| 440 |  |  |  |  |  |  | use Moose; | 
| 441 |  |  |  |  |  |  | use MooseX::MethodAttributes; | 
| 442 |  |  |  |  |  |  | use Types::Standard qw/Int Str/; | 
| 443 |  |  |  |  |  |  |  | 
| 444 |  |  |  |  |  |  | extends 'Catalyst::Controller'; | 
| 445 |  |  |  |  |  |  | with 'Catalyst::ControllerRole::At'; | 
| 446 |  |  |  |  |  |  |  | 
| 447 |  |  |  |  |  |  | sub query :At($action?{name:Str}{age:Int}) { | 
| 448 |  |  |  |  |  |  | my ($self, $c, $id) = @_; | 
| 449 |  |  |  |  |  |  | } | 
| 450 |  |  |  |  |  |  |  | 
| 451 |  |  |  |  |  |  | __PACKAGE__->meta->make_immutable; | 
| 452 |  |  |  |  |  |  |  | 
| 453 |  |  |  |  |  |  | This would match 'https://example/query?name=john;age=47'. | 
| 454 |  |  |  |  |  |  |  | 
| 455 |  |  |  |  |  |  | Your query keys will appear in the %_ in the same way as all your named arguments. | 
| 456 |  |  |  |  |  |  |  | 
| 457 |  |  |  |  |  |  | You do not need to use a type constraint on the query parameters.  If you do not do so | 
| 458 |  |  |  |  |  |  | all that is required is that the requested query parameters exist. | 
| 459 |  |  |  |  |  |  |  | 
| 460 |  |  |  |  |  |  | This uses the ActionRole L<Catalyst::ActionRole::QueryParameter> under the hood, which | 
| 461 |  |  |  |  |  |  | you may wish to review for more details. | 
| 462 |  |  |  |  |  |  |  | 
| 463 |  |  |  |  |  |  | =head2 Chaining Actions inside a Controller | 
| 464 |  |  |  |  |  |  |  | 
| 465 |  |  |  |  |  |  | L<Catalyst> action chaining allows you to spread the logic associated with a given URL | 
| 466 |  |  |  |  |  |  | across a set of actions which all are responsible for handling a part of the URL | 
| 467 |  |  |  |  |  |  | template.  The idea is to allow you to better decompose your logic to promote clarity | 
| 468 |  |  |  |  |  |  | and reuse.  However the built-in syntax for declaring action chains is difficult for | 
| 469 |  |  |  |  |  |  | many people to use.  Here's how you do it with L<Catalyst::ControllerRole::At> | 
| 470 |  |  |  |  |  |  |  | 
| 471 |  |  |  |  |  |  | Starting a Chain of actions is straightforward.  you just add '/...' to the end of your | 
| 472 |  |  |  |  |  |  | path specification.  This is to indicate that the action expects more parts 'to follow'. | 
| 473 |  |  |  |  |  |  | For example: | 
| 474 |  |  |  |  |  |  |  | 
| 475 |  |  |  |  |  |  | package MyApp::Controller::Example; | 
| 476 |  |  |  |  |  |  |  | 
| 477 |  |  |  |  |  |  | use Moose; | 
| 478 |  |  |  |  |  |  | use MooseX::MethodAttributes; | 
| 479 |  |  |  |  |  |  | use Types::Standard qw/Int Str/; | 
| 480 |  |  |  |  |  |  |  | 
| 481 |  |  |  |  |  |  | extends 'Catalyst::Controller'; | 
| 482 |  |  |  |  |  |  | with 'Catalyst::ControllerRole::At'; | 
| 483 |  |  |  |  |  |  |  | 
| 484 |  |  |  |  |  |  | sub init :At($controller/...) { ... } | 
| 485 |  |  |  |  |  |  |  | 
| 486 |  |  |  |  |  |  | __PACKAGE__->meta->make_immutable; | 
| 487 |  |  |  |  |  |  |  | 
| 488 |  |  |  |  |  |  | The action 'init' starts a new chain of actions and declares the first part of the | 
| 489 |  |  |  |  |  |  | definition, 'https://localhost/example/...'.  You continue a chain in the same way, | 
| 490 |  |  |  |  |  |  | but you need to specify the parent action that is being continued using the 'Via' | 
| 491 |  |  |  |  |  |  | attribute.  You terminate a chain when you define an action that doesn't declare '...' | 
| 492 |  |  |  |  |  |  | as the last path.  For example: | 
| 493 |  |  |  |  |  |  |  | 
| 494 |  |  |  |  |  |  | sub init :At($controller/...) { | 
| 495 |  |  |  |  |  |  | my ($self, $c) = @_; | 
| 496 |  |  |  |  |  |  | } | 
| 497 |  |  |  |  |  |  |  | 
| 498 |  |  |  |  |  |  | sub next :Via(init) At({}/...) { | 
| 499 |  |  |  |  |  |  | my ($self, $c, $arg) = @_; | 
| 500 |  |  |  |  |  |  | } | 
| 501 |  |  |  |  |  |  |  | 
| 502 |  |  |  |  |  |  | sub last :Via(next) At({}) { | 
| 503 |  |  |  |  |  |  | my ($self, $c, $arg) = @_; | 
| 504 |  |  |  |  |  |  | } | 
| 505 |  |  |  |  |  |  |  | 
| 506 |  |  |  |  |  |  | This defines an action chain with three 'stops' which matches a URL like (for example) | 
| 507 |  |  |  |  |  |  | 'https://localhost/$controller/arg1/arg2'.  Each action will get executed for the matching | 
| 508 |  |  |  |  |  |  | part, and will get arguments as defined in their match specification. | 
| 509 |  |  |  |  |  |  |  | 
| 510 |  |  |  |  |  |  | B<NOTE> The 'Via' attribute must contain a value. | 
| 511 |  |  |  |  |  |  |  | 
| 512 |  |  |  |  |  |  | When chaining you can use (or not) any mix of type constraints on your arguments, named | 
| 513 |  |  |  |  |  |  | arguments, and query parameter matching.  Here's a full example: | 
| 514 |  |  |  |  |  |  |  | 
| 515 |  |  |  |  |  |  | package MyApp::Controller::Example; | 
| 516 |  |  |  |  |  |  |  | 
| 517 |  |  |  |  |  |  | use Moose; | 
| 518 |  |  |  |  |  |  | use MooseX::MethodAttributes; | 
| 519 |  |  |  |  |  |  | use Types::Standard qw/Int/; | 
| 520 |  |  |  |  |  |  |  | 
| 521 |  |  |  |  |  |  | extends 'Catalyst::Controller'; | 
| 522 |  |  |  |  |  |  | with 'Catalyst::ControllerRole::At'; | 
| 523 |  |  |  |  |  |  |  | 
| 524 |  |  |  |  |  |  | sub init :At($controller/...) { ... } | 
| 525 |  |  |  |  |  |  |  | 
| 526 |  |  |  |  |  |  | sub next :Via(init) At({id:Int}/...) { | 
| 527 |  |  |  |  |  |  | my ($self, $c, $int_id) = @_; | 
| 528 |  |  |  |  |  |  | } | 
| 529 |  |  |  |  |  |  |  | 
| 530 |  |  |  |  |  |  | sub last :Via(next) At({id:Int}?{q}) { | 
| 531 |  |  |  |  |  |  | my ($self, $c, $int_id) = @_; | 
| 532 |  |  |  |  |  |  | } | 
| 533 |  |  |  |  |  |  |  | 
| 534 |  |  |  |  |  |  | __PACKAGE__->meta->make_immutable; | 
| 535 |  |  |  |  |  |  |  | 
| 536 |  |  |  |  |  |  | =head2 Actions in a Chain with no match template | 
| 537 |  |  |  |  |  |  |  | 
| 538 |  |  |  |  |  |  | Sometimes for the purposes of organizing code you will have an action that is a | 
| 539 |  |  |  |  |  |  | midpoint in a chain that does not match any part of a URL template.  For that | 
| 540 |  |  |  |  |  |  | case you can omit the path and argument match specification.  For example: | 
| 541 |  |  |  |  |  |  |  | 
| 542 |  |  |  |  |  |  | package MyApp::Controller::Example; | 
| 543 |  |  |  |  |  |  |  | 
| 544 |  |  |  |  |  |  | use Moose; | 
| 545 |  |  |  |  |  |  | use MooseX::MethodAttributes; | 
| 546 |  |  |  |  |  |  | use Types::Standard qw/Int/; | 
| 547 |  |  |  |  |  |  |  | 
| 548 |  |  |  |  |  |  | extends 'Catalyst::Controller'; | 
| 549 |  |  |  |  |  |  | with 'Catalyst::ControllerRole::At'; | 
| 550 |  |  |  |  |  |  |  | 
| 551 |  |  |  |  |  |  | sub init :At($controller/...) { ... } | 
| 552 |  |  |  |  |  |  |  | 
| 553 |  |  |  |  |  |  | sub middle :Via(init) At(...) { | 
| 554 |  |  |  |  |  |  | my ($self, $c) = @_; | 
| 555 |  |  |  |  |  |  | } | 
| 556 |  |  |  |  |  |  |  | 
| 557 |  |  |  |  |  |  | sub last :Via(next) At({id:Int}) { | 
| 558 |  |  |  |  |  |  | my ($self, $c, $id) = @_; | 
| 559 |  |  |  |  |  |  | } | 
| 560 |  |  |  |  |  |  |  | 
| 561 |  |  |  |  |  |  | __PACKAGE__->meta->make_immutable; | 
| 562 |  |  |  |  |  |  |  | 
| 563 |  |  |  |  |  |  | This will match a URL like 'https://localhost/example/100'. | 
| 564 |  |  |  |  |  |  |  | 
| 565 |  |  |  |  |  |  | B<NOTE> If you declare a Via but not At, this is an error.  You must | 
| 566 |  |  |  |  |  |  | always provide an At(), even in the case of a terminal action with no | 
| 567 |  |  |  |  |  |  | match parts of it own.  For example: | 
| 568 |  |  |  |  |  |  |  | 
| 569 |  |  |  |  |  |  | package MyApp::Controller::Example; | 
| 570 |  |  |  |  |  |  |  | 
| 571 |  |  |  |  |  |  | use Moose; | 
| 572 |  |  |  |  |  |  | use MooseX::MethodAttributes; | 
| 573 |  |  |  |  |  |  |  | 
| 574 |  |  |  |  |  |  | extends 'Catalyst::Controller'; | 
| 575 |  |  |  |  |  |  | with 'Catalyst::ControllerRole::At'; | 
| 576 |  |  |  |  |  |  |  | 
| 577 |  |  |  |  |  |  | sub first :At($controller/...) { ... } | 
| 578 |  |  |  |  |  |  |  | 
| 579 |  |  |  |  |  |  | sub second :Via(first) At(...) { | 
| 580 |  |  |  |  |  |  | my ($self, $c) = @_; | 
| 581 |  |  |  |  |  |  | } | 
| 582 |  |  |  |  |  |  |  | 
| 583 |  |  |  |  |  |  | sub third :Via(second) At(...) { | 
| 584 |  |  |  |  |  |  | my ($self, $c) = @_; | 
| 585 |  |  |  |  |  |  | } | 
| 586 |  |  |  |  |  |  |  | 
| 587 |  |  |  |  |  |  | sub last :Via(third) At() { | 
| 588 |  |  |  |  |  |  | my ($self, $c, $id) = @_; | 
| 589 |  |  |  |  |  |  | } | 
| 590 |  |  |  |  |  |  |  | 
| 591 |  |  |  |  |  |  | __PACKAGE__->meta->make_immutable; | 
| 592 |  |  |  |  |  |  |  | 
| 593 |  |  |  |  |  |  | This creates a chained action that matches 'http://localhost/example' but calls | 
| 594 |  |  |  |  |  |  | each of the three actions in the chain in order.  Although it might seem odd to | 
| 595 |  |  |  |  |  |  | create an action that is not connected to a path part of a URL request, you might find | 
| 596 |  |  |  |  |  |  | cases where this results in well factored and reusable controllers. | 
| 597 |  |  |  |  |  |  |  | 
| 598 |  |  |  |  |  |  | B<NOTE> For the purposes of executing code, we treat 'At' and 'At()' as the same.  However | 
| 599 |  |  |  |  |  |  | We highly recommend At() as a best practice since it more clearly represents the idea | 
| 600 |  |  |  |  |  |  | of 'no match template'. | 
| 601 |  |  |  |  |  |  |  | 
| 602 |  |  |  |  |  |  | =head2 Chaining Actions across Controllers | 
| 603 |  |  |  |  |  |  |  | 
| 604 |  |  |  |  |  |  | The method attributes 'Via()' contains a pointer to the action being continued.  In | 
| 605 |  |  |  |  |  |  | standard practice this is almost always the name of an action in the same controller | 
| 606 |  |  |  |  |  |  | as the one declaring it.  This could be said to be a 'relative' (as in relative to | 
| 607 |  |  |  |  |  |  | the current controller) action.  However you don't have to use a relative name.  You | 
| 608 |  |  |  |  |  |  | can use any action's absolute private name, as long as it is an action that declares itself | 
| 609 |  |  |  |  |  |  | to be a link in a chain. | 
| 610 |  |  |  |  |  |  |  | 
| 611 |  |  |  |  |  |  | However in practice it is not alway a good idea to spread your chained acions across | 
| 612 |  |  |  |  |  |  | across controllers in a manner that is not easy to follow.  We recommend you try | 
| 613 |  |  |  |  |  |  | to limit youself to chains that follow the controller hierarchy, which should be | 
| 614 |  |  |  |  |  |  | easier for your code maintainers. | 
| 615 |  |  |  |  |  |  |  | 
| 616 |  |  |  |  |  |  | For this common, best practice case when you are continuing your chained actions across | 
| 617 |  |  |  |  |  |  | controllers, following a controller hierarchy, we provide some template expansions you can | 
| 618 |  |  |  |  |  |  | use in the 'Via' attribute.  These are useful to enforce this best practice as well as | 
| 619 |  |  |  |  |  |  | promote reusability by decoupling hard coded private action namespaces from your controller. | 
| 620 |  |  |  |  |  |  |  | 
| 621 |  |  |  |  |  |  | $up: The controller whose namespace contains the current controller | 
| 622 |  |  |  |  |  |  | $name The name of the current actions subroutine | 
| 623 |  |  |  |  |  |  | $parent: Expands to $up/$subname | 
| 624 |  |  |  |  |  |  |  | 
| 625 |  |  |  |  |  |  | For example: | 
| 626 |  |  |  |  |  |  |  | 
| 627 |  |  |  |  |  |  | package MyApp::Controller::ThingsTodo; | 
| 628 |  |  |  |  |  |  |  | 
| 629 |  |  |  |  |  |  | use Moose; | 
| 630 |  |  |  |  |  |  | use MooseX::MethodAttributes; | 
| 631 |  |  |  |  |  |  |  | 
| 632 |  |  |  |  |  |  | extends 'Catalyst::Controller'; | 
| 633 |  |  |  |  |  |  | with 'Catalyst::ControllerRole::At'; | 
| 634 |  |  |  |  |  |  |  | 
| 635 |  |  |  |  |  |  | sub init :At($controller/...) { | 
| 636 |  |  |  |  |  |  | my ($self, $c) = @_; | 
| 637 |  |  |  |  |  |  | } | 
| 638 |  |  |  |  |  |  |  | 
| 639 |  |  |  |  |  |  | sub list :Via(init) At($name) { | 
| 640 |  |  |  |  |  |  | my ($self, $c) = @_; | 
| 641 |  |  |  |  |  |  | } | 
| 642 |  |  |  |  |  |  |  | 
| 643 |  |  |  |  |  |  | __PACKAGE__->meta->make_immutable; | 
| 644 |  |  |  |  |  |  |  | 
| 645 |  |  |  |  |  |  | package MyApp::Controller::ThingsTodo::Item; | 
| 646 |  |  |  |  |  |  |  | 
| 647 |  |  |  |  |  |  | use Moose; | 
| 648 |  |  |  |  |  |  | use MooseX::MethodAttributes; | 
| 649 |  |  |  |  |  |  |  | 
| 650 |  |  |  |  |  |  | extends 'Catalyst::Controller'; | 
| 651 |  |  |  |  |  |  | with 'Catalyst::ControllerRole::At'; | 
| 652 |  |  |  |  |  |  |  | 
| 653 |  |  |  |  |  |  | sub init :Via($parent) At({id:Int}/...) { | 
| 654 |  |  |  |  |  |  | my ($self, $c) = @_; | 
| 655 |  |  |  |  |  |  | } | 
| 656 |  |  |  |  |  |  |  | 
| 657 |  |  |  |  |  |  | sub show    :Via(init) At($name) { ... } | 
| 658 |  |  |  |  |  |  | sub update  :Via(init) At($name) { ... } | 
| 659 |  |  |  |  |  |  | sub delete  :Via(init) At($name) { ... } | 
| 660 |  |  |  |  |  |  |  | 
| 661 |  |  |  |  |  |  | __PACKAGE__->meta->make_immutable; | 
| 662 |  |  |  |  |  |  |  | 
| 663 |  |  |  |  |  |  | This creates four (4) URL templates: | 
| 664 |  |  |  |  |  |  |  | 
| 665 |  |  |  |  |  |  | https://localhost/thingstodo/list | 
| 666 |  |  |  |  |  |  | https://localhost/thingstodo/:id/show | 
| 667 |  |  |  |  |  |  | https://localhost/thingstodo/:id/update | 
| 668 |  |  |  |  |  |  | https://localhost/thingstodo/:id/delete | 
| 669 |  |  |  |  |  |  |  | 
| 670 |  |  |  |  |  |  | With an action execution flow as follows: | 
| 671 |  |  |  |  |  |  |  | 
| 672 |  |  |  |  |  |  | https://localhost/thingstodo/list => | 
| 673 |  |  |  |  |  |  | /thingstodo/init | 
| 674 |  |  |  |  |  |  | /thingstodo/list | 
| 675 |  |  |  |  |  |  |  | 
| 676 |  |  |  |  |  |  | https://localhost/thingstodo/:id/show | 
| 677 |  |  |  |  |  |  | /thingstodo/init | 
| 678 |  |  |  |  |  |  | /thingstodo/item/init | 
| 679 |  |  |  |  |  |  | /thingstodo/item/show | 
| 680 |  |  |  |  |  |  |  | 
| 681 |  |  |  |  |  |  | https://localhost/thingstodo/:id/update | 
| 682 |  |  |  |  |  |  | /thingstodo/init | 
| 683 |  |  |  |  |  |  | /thingstodo/item/init | 
| 684 |  |  |  |  |  |  | /thingstodo/item/update | 
| 685 |  |  |  |  |  |  |  | 
| 686 |  |  |  |  |  |  | https://localhost/thingstodo/:id/delete | 
| 687 |  |  |  |  |  |  | /thingstodo/init | 
| 688 |  |  |  |  |  |  | /thingstodo/item/init | 
| 689 |  |  |  |  |  |  | /thingstodo/item/delete | 
| 690 |  |  |  |  |  |  |  | 
| 691 |  |  |  |  |  |  | =head2 Method Shortcuts | 
| 692 |  |  |  |  |  |  |  | 
| 693 |  |  |  |  |  |  | Its common today to want to be able to match a URL to a specific HTTP method.  For example | 
| 694 |  |  |  |  |  |  | you might want to match a GET request to one action and a POST request to another.  L<Catalyst> | 
| 695 |  |  |  |  |  |  | offers the C<Method> attribute as well as shortcuts: C<GET>, C<POST>, C<PUT>, C<DELETE>, C<HEAD>, | 
| 696 |  |  |  |  |  |  | C<OPTIONS>.  To tidy your method declarations you can use C<Get>, C<Post>, C<Put>, C<Delete>, C<Head>, | 
| 697 |  |  |  |  |  |  | C<Options> in place of C<At>: | 
| 698 |  |  |  |  |  |  |  | 
| 699 |  |  |  |  |  |  | package MyApp::Controller::Example; | 
| 700 |  |  |  |  |  |  |  | 
| 701 |  |  |  |  |  |  | use Moose; | 
| 702 |  |  |  |  |  |  | use MooseX::MethodAttributes; | 
| 703 |  |  |  |  |  |  |  | 
| 704 |  |  |  |  |  |  | extends 'Catalyst::Controller'; | 
| 705 |  |  |  |  |  |  | with 'Catalyst::ControllerRole::At'; | 
| 706 |  |  |  |  |  |  |  | 
| 707 |  |  |  |  |  |  | sub get :Get($controller/...) { ... } | 
| 708 |  |  |  |  |  |  | sub post :Post($controller/...) { ... } | 
| 709 |  |  |  |  |  |  | sub put :Put($controller/...) { ... } | 
| 710 |  |  |  |  |  |  | sub delete :Delete($controller/...) { ... } | 
| 711 |  |  |  |  |  |  | sub head :Head($controller/...) { ... } | 
| 712 |  |  |  |  |  |  | sub options :Options($controller/...) { ... } | 
| 713 |  |  |  |  |  |  |  | 
| 714 |  |  |  |  |  |  | __PACKAGE__->meta->make_immutable; | 
| 715 |  |  |  |  |  |  |  | 
| 716 |  |  |  |  |  |  | Basically: | 
| 717 |  |  |  |  |  |  |  | 
| 718 |  |  |  |  |  |  | sub get :Get($controller/...) { ... } | 
| 719 |  |  |  |  |  |  |  | 
| 720 |  |  |  |  |  |  | Is the same as: | 
| 721 |  |  |  |  |  |  |  | 
| 722 |  |  |  |  |  |  | sub get :GET At($controller/...) { ... } | 
| 723 |  |  |  |  |  |  |  | 
| 724 |  |  |  |  |  |  | You may find the few characters saved worth it or not.   The choice is yours. | 
| 725 |  |  |  |  |  |  |  | 
| 726 |  |  |  |  |  |  | =head1 COOKBOOK | 
| 727 |  |  |  |  |  |  |  | 
| 728 |  |  |  |  |  |  | One thing I like to do is create a base controller for my project | 
| 729 |  |  |  |  |  |  | so that I can make my controllers more concise: | 
| 730 |  |  |  |  |  |  |  | 
| 731 |  |  |  |  |  |  | package Myapp::Controller; | 
| 732 |  |  |  |  |  |  |  | 
| 733 |  |  |  |  |  |  | use Moose; | 
| 734 |  |  |  |  |  |  | extends 'Catalyst::Controller'; | 
| 735 |  |  |  |  |  |  | with 'Catalyst::ControllerRole::At'; | 
| 736 |  |  |  |  |  |  |  | 
| 737 |  |  |  |  |  |  | __PACKAGE__->meta->make_immutable; | 
| 738 |  |  |  |  |  |  |  | 
| 739 |  |  |  |  |  |  | You can of course doa  lot more here if you want but I usually recommend | 
| 740 |  |  |  |  |  |  | the lightest touch possible in your base controllers since the more you customize | 
| 741 |  |  |  |  |  |  | the harder it might be for people new to the code to debug the system. | 
| 742 |  |  |  |  |  |  |  | 
| 743 |  |  |  |  |  |  | =head1 TODO | 
| 744 |  |  |  |  |  |  |  | 
| 745 |  |  |  |  |  |  | - HTTP Methods | 
| 746 |  |  |  |  |  |  | - Incoming Content type matching | 
| 747 |  |  |  |  |  |  | - ??Content Negotiation?? | 
| 748 |  |  |  |  |  |  |  | 
| 749 |  |  |  |  |  |  | =head1 AUTHOR | 
| 750 |  |  |  |  |  |  |  | 
| 751 |  |  |  |  |  |  | John Napiorkowski L<email:jjnapiork@cpan.org> | 
| 752 |  |  |  |  |  |  |  | 
| 753 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 754 |  |  |  |  |  |  |  | 
| 755 |  |  |  |  |  |  | L<Catalyst>, L<Catalyst::Controller>. | 
| 756 |  |  |  |  |  |  |  | 
| 757 |  |  |  |  |  |  | =head1 COPYRIGHT & LICENSE | 
| 758 |  |  |  |  |  |  |  | 
| 759 |  |  |  |  |  |  | Copyright 2016, John Napiorkowski L<email:jjnapiork@cpan.org> | 
| 760 |  |  |  |  |  |  |  | 
| 761 |  |  |  |  |  |  | This library is free software; you can redistribute it and/or modify it under | 
| 762 |  |  |  |  |  |  | the same terms as Perl itself. | 
| 763 |  |  |  |  |  |  |  | 
| 764 |  |  |  |  |  |  | =cut |