File Coverage

blib/lib/Catalyst/ActionRole/Methods.pm
Criterion Covered Total %
statement 28 50 56.0
branch 8 14 57.1
condition 3 5 60.0
subroutine 4 8 50.0
pod 1 1 100.0
total 44 78 56.4


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