File Coverage

blib/lib/Catalyst/ActionRole/Methods.pm
Criterion Covered Total %
statement 41 50 82.0
branch 12 18 66.6
condition 4 5 80.0
subroutine 6 8 75.0
pod 1 1 100.0
total 64 82 78.0


line stmt bran cond sub pod time code
1              
2             use Moose::Role;
3 1     1   393399  
  1         2  
  1         8  
4             our $VERSION = '0.003';
5              
6             around 'dispatch', sub {
7             my $orig = shift;
8             my $self = shift;
9             my $c = shift;
10              
11             my $return = $self->$orig($c, @_);
12             my $rest_method = $self->name . "_" . uc( $c->request->method );
13             my $sub_return = $self->_dispatch_rest_method( $c, $rest_method );
14              
15             return defined($sub_return) ? $sub_return : $return;
16             };
17              
18             around 'list_extra_info' => sub {
19             my ($orig, $self, @args) = @_;
20             my @allowed_methods = sort $self->get_allowed_methods($self->class,undef,$self->name);
21             return +{
22             %{ $self->$orig(@args) },
23             HTTP_METHODS => \@allowed_methods,
24             };
25             };
26            
27             my $self = shift;
28             my $c = shift;
29 6     6   10 my $rest_method = shift;
30 6         10  
31 6         10 my $req = $c->request;
32             my $controller = $c->component( $self->class );
33 6         105 my ($code, $name);
34 6         144
35 6         234 # Common case, for foo_GET etc
36             if ( $code = $controller->action_for($rest_method) ) {
37             return $c->forward( $code, $req->args ); # Forward to foo_GET if it's an action
38 6 100       26 }
    100          
39 2         358 elsif ($code = $controller->can($rest_method)) {
40             $name = $rest_method; # Stash name and code to run 'foo_GET' like an action below.
41             }
42 1         177
43             # Generic handling for foo_*
44             if (!$code) {
45             my $code_action = {
46 4 100       550 OPTIONS => sub {
47             $name = $rest_method;
48             $code = sub { $self->_return_options($self->name, @_) };
49 0     0   0 },
50 0         0 HEAD => sub {
  0         0  
51             $rest_method =~ s{_HEAD$}{_GET}i;
52             $self->_dispatch_rest_method($c, $rest_method);
53 1     1   5 },
54 1         7 default => sub {
55             # Otherwise, not implemented.
56             $name = $self->name . "_not_implemented";
57             $code = $controller->can($name) # User method
58 2     2   39 # Generic not implemented
59             || sub { $self->_return_not_implemented($self->name, @_) };
60             },
61 2   100     28 };
62             my ( $http_method, $action_name ) = ( $rest_method, $self->name );
63 3         32 $http_method =~ s{\Q$action_name\E\_}{};
64 3         65 my $respond = ($code_action->{$http_method}
65 3         55 || $code_action->{'default'})->();
66             return $respond unless $name;
67 3   66     28 }
68 3 100       502
69             # localise stuff so we can dispatch the action 'as normal, but get
70             # different stats shown, and different code run.
71             # Also get the full path for the action, and make it look like a forward
72             local $self->{code} = $code;
73             my @name = split m{/}, $self->reverse;
74 3         9 $name[-1] = $name;
75 3         63 local $self->{reverse} = "-> " . join('/', @name);
76 3         22
77 3         13 $c->execute( $self->class, $self, @{ $req->args } );
78             }
79 3         75
  3         22  
80             my ( $self, $controller, $c, $name ) = @_;
81             my $class = ref($controller) ? ref($controller) : $controller;
82             my $methods = {
83 1     1 1 4 map { /^$name\_(.+)$/ ? ( $1 => 1 ) : () }
84 1 50       4 ($class->meta->get_all_method_names )
85             };
86 1 50       9 $methods->{'HEAD'} = 1 if $methods->{'GET'};
  96         3736  
87             delete $methods->{'not_implemented'};
88             return sort keys %$methods;
89 1 50       23 }
90 1         3
91 1         7 my ( $self, $method_name, $controller, $c) = @_;
92             my @allowed = $self->get_allowed_methods($controller, $c, $method_name);
93             $c->response->content_type('text/plain');
94             $c->response->status(200);
95 0     0   0 $c->response->header( 'Allow' => @allowed ? \@allowed : '' );
96 0         0 $c->response->body(q{});
97 0         0 }
98 0         0
99 0 0       0 my ( $self, $method_name, $controller, $c ) = @_;
100 0         0
101             my @allowed = $self->get_allowed_methods($controller, $c, $method_name);
102             $c->response->content_type('text/plain');
103             $c->response->status(405);
104 1     1   8 $c->response->header( 'Allow' => @allowed ? \@allowed : '' );
105             $c->response->body( "Method "
106 1         4 . $c->request->method
107 1         29 . " not implemented for "
108 1         239 . $c->uri_for( $method_name ) );
109 1 50       108 }
110 1         219  
111             1;
112              
113             =head1 NAME
114              
115             Catalyst::ActionRole::Methods - Dispatch by HTTP Methods
116              
117             =head1 SYNOPSIS
118              
119             package MyApp::Controller::Example;
120              
121             use Moose;
122             use MooseX::MethodAttributes;
123              
124             extends 'Catalyst::Controller';
125              
126             sub myaction :Chained(/) Does('Methods') CaptureArgs(1) {
127             my ($self, $c, $arg) = @_;
128             # When this action is matched, first execute this action's
129             # body, then an action matching the HTTP method or the not
130             # implemented one if needed.
131             }
132              
133             sub myaction_GET :Action {
134             my ($self, $c, $arg) = @_;
135             # Note that if the 'parent' action has args or capture-args, those are
136             # made available to a matching method action.
137             }
138              
139             sub myaction_POST {
140             my ($self, $c, $arg) = @_;
141             # We match the subroutine name whether its an action or not. If you
142             # make it an action, as in the _GET above, you are allowed to apply
143             # action roles (which is the main advantage to this AFAIK).
144             }
145              
146             sub myaction_not_implemented {
147             my ($self, $c, $arg) = @_;
148             # There's a sane default for this, but you can override as needed.
149             }
150              
151             sub next_action_in_chain_1 :Chained(myaction) Args(0) { ... }
152              
153             sub next_action_in_chain_2 :Chained(myaction) Args(0) { ... }
154              
155             __PACKAGE__->meta->make_immutable;
156              
157             =head1 DESCRIPTION
158              
159             This is a L<Moose::Role> version of the classic L<Catalyst::Action::REST> action
160             class. The intention is to offer some of the popular functionality that comes
161             with L<Catalyst::Action::REST> in a more modular, 'build what you need' package.
162              
163             Bulk of this documentation and test cases derive from L<Catalyst::Action::REST>
164             with the current author's gratitude.
165              
166             This Action Role handles doing automatic method dispatching for requests. It
167             takes a normal Catalyst action, and changes the dispatch to append an
168             underscore and method name. First it will try dispatching to an action with
169             the generated name, and failing that it will try to dispatch to a regular
170             method.
171              
172             sub foo :Local :Does('Methods') {
173             ... do setup for HTTP method specific handlers ...
174             }
175            
176             sub foo_GET {
177             ... do something for GET requests ...
178             }
179            
180             # alternatively use an Action
181             sub foo_PUT : Action {
182             ... do something for PUT requests ...
183             }
184            
185             For example, in the example above, calling GET on "/foo" would result in
186             the foo_GET method being dispatched.
187            
188             If a method is requested that is not implemented, this action will
189             return a status 405 (Method Not Found). It will populate the "Allow" header
190             with the list of implemented request methods. You can override this behavior
191             by implementing a custom 405 handler like so:
192            
193             sub foo_not_implemented {
194             ... handle not implemented methods ...
195             }
196            
197             If you do not provide an _OPTIONS subroutine, we will automatically respond
198             with a 200 OK. The "Allow" header will be populated with the list of
199             implemented request methods. If you do not provide an _HEAD either, we will
200             auto dispatch to the _GET one in case it exists.
201              
202             =head1 VERSUS Catalyst::Action::REST
203              
204             L<Catalyst::Action::REST> works fine doesn't it? Why offer a new approach? There's
205             a few reasons:
206              
207             First, when L<Catalyst::Action::REST> was written we did not have
208             L<Moose> and the only way to augment functionality was via inheritance. Now that
209             L<Moose> is common we instead say that it is typically better to use a L<Moose::Role>
210             to augment a class function rather to use a subclass. The role approach is a smaller
211             hammer and it plays nicer when you need to combine several roles to augment a class
212             (as compared to multiple inheritance approaches.). This is why we brought support for
213             action roles into core L<Catalyst::Controller> several years ago. Letting you have
214             this functionality via a role should lead to more flexible systems that play nice
215             with other roles. One nice side effect of this 'play nice with others' is that we
216             were able to hook into the 'list_extra_info' method of the core action class so that
217             you can now see in your developer mode debug output the matched http methods, for
218             example:
219              
220             .-------------------------------------+----------------------------------------.
221             | Path Spec | Private |
222             +-------------------------------------+----------------------------------------+
223             | /myaction/*/next_action_in_chain | GET, HEAD, POST /myaction (1) |
224             | | => /next_action_in_chain (0) |
225             '-------------------------------------+----------------------------------------'
226              
227             This is not to say its never correct to use an action class, but now you have the
228             choice.
229              
230             Second, L<Catalyst::Action::REST> has the behavior as noted of altering the core
231             L<Catalyst::Request> class. This might not be desired and has always struck the
232             author as a bit too much side effect / action at a distance.
233              
234             Last, L<Catalyst::Action::REST> is actually a larger distribution with a bunch of
235             other features and dependencies that you might not want. The intention is to offer
236             those bits of functionality as standalone, modern components and allow one to assemble
237             the parts needed, as needed.
238              
239             This action role is for the most part a 1-1 port of the action class, with one minor
240             change to reduce the dependency count. Additionally, it does not automatically
241             apply the L<Catalyst::Request::REST> action class to your global L<Catalyst>
242             action class. This feature is left off because its easy to set this yourself if
243             desired via the global L<Catalyst> configuration and we want to follow and promote
244             the idea of 'do one thing well and nothing surprising'.
245              
246             B<NOTE> There is an additional minor change in how we handle return values from actions. In
247             general L<Catalyst> does nothing with an action return value (unless in an auto action).
248             However this might not always be the future case, and you might have used that return value
249             for something in your custom code. In L<Catalyst::Action::REST> the return value was
250             always the return of the dispatched sub action (if any). We tweaked this so that we use
251             the sub action return value, BUT if that value is undefined, we use the parent action
252             return value instead.
253              
254             We also dropped saying 'REST' when all we are doing is dispatching on HTTP method.
255             Since the time that the first version of L<Catalysts::Action::REST> was released to
256             CPAN our notion of what 'REST' means has greatly evolved so I think its correct to
257             change the name to be functionality specific and to not confuse people that are new
258             to the REST discipline.
259              
260             This action role is intended to be used in all the places
261             you used to use the action class and have the same results, with the exception
262             of the already mentioned 'not messing with the global request class'. However
263             L<Catalyst::Action::REST> has been around for a long time and is well vetted in
264             production so I would caution care with changing your mission critical systems
265             very quickly.
266              
267             =head1 VERSUS NATIVE METHOD ATTRIBUTES
268              
269             L<Catalyst> since version 5.90030 has offered a core approach to dispatch on the
270             http method (via L<Catalyst::ActionRole::HTTPMethods>). Why still use this action role
271             versus the core functionality? ALthough it partly comes down to preference and the
272             author's desire to give current users of L<Catalyst::Action::REST> a path forward, there
273             is some functionality differences beetween the two which may recommend one over the
274             other. For example the core method matching does not offer an automatic default
275             'Not Implemented' response that correctly sets the OPTIONS header. Also the dispatch
276             flow between the two approaches is different and when using chained actions one
277             might be a better choice over the other depending on how your chains are arranged and
278             your desired flow of action.
279              
280             =head1 METHODS
281            
282             This role contains the following methods.
283              
284             =head2 get_allowed_methods
285              
286             Returns a list of the allowed methods.
287              
288             =head2 dispatch
289            
290             This method overrides the default dispatch mechanism to the re-dispatching
291             mechanism described above.
292              
293             =head1 AUTHOR
294              
295             John Napiorkowski <jnapiork@cpan.org>
296              
297             Author list from L<Catalyst::Action::REST>
298            
299             Adam Jacob E<lt>adam@stalecoffee.orgE<gt>, with lots of help from mst and jrockway
300             Marchex, Inc. paid me while I developed this module. (L<http://www.marchex.com>)
301            
302             =head1 CONTRIBUTORS
303              
304             The following contributor list was copied from L<Catalyst::Action::REST>
305             from where the bulk of this code was copied.
306            
307             Tomas Doran (t0m) E<lt>bobtfish@bobtfish.netE<gt>
308            
309             John Goulah
310            
311             Christopher Laco
312            
313             Daisuke Maki E<lt>daisuke@endeworks.jpE<gt>
314            
315             Hans Dieter Pearcey
316            
317             Brian Phillips E<lt>bphillips@cpan.orgE<gt>
318            
319             Dave Rolsky E<lt>autarch@urth.orgE<gt>
320            
321             Luke Saunders
322            
323             Arthur Axel "fREW" Schmidt E<lt>frioux@gmail.comE<gt>
324            
325             J. Shirley E<lt>jshirley@gmail.comE<gt>
326            
327             Gavin Henry E<lt>ghenry@surevoip.co.ukE<gt>
328            
329             Gerv http://www.gerv.net/
330            
331             Colin Newell <colin@opusvl.com>
332            
333             Wallace Reis E<lt>wreis@cpan.orgE<gt>
334            
335             André Walker (andrewalker) <andre@cpan.org>
336            
337             =head1 COPYRIGHT
338            
339             Copyright (c) 2006-2015 the above named AUTHOR and CONTRIBUTORS
340            
341             =head1 LICENSE
342            
343             You may distribute this code under the same terms as Perl itself.
344            
345             =cut