File Coverage

blib/lib/Catalyst/Plugin/ForwardChained.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Catalyst::Plugin::ForwardChained;
2              
3             =head1 NAME
4              
5             Catalyst::Plugin::ForwardChained - Forwarding to "Chain"-Actions in Catalyst
6              
7             =head1 DESCRIPTION
8              
9             Forwarding to the end point of a couple of chain methods ..
10              
11             In most cases: dont use - better user redirect instead
12              
13             This is a hackaround, not a clean solution.
14              
15             Experimental.
16              
17             =head1 SYNOPSIS
18              
19             # In your application class
20             use Catalyst qw/ ForwardChained /;
21            
22             # ... somwhere else:
23             $c->forward_to_chained( [ qw/ chained endpoint /, [ qw/ args / ] );
24             $c->forward_to_chained( 'chained/endpoint', [ qw/ args / ] );
25              
26              
27             =head2 Example 1
28              
29             Having some controller:
30              
31             package MyApp::Controller::Test;
32            
33             # ..
34             # to be clear :
35             __PACKAGE__->config->{ namespace } = 'test';
36            
37             # url would be "/one/*"
38             sub my_index : PathPart( 'one' ) : Chained( '/' ) : CaptureArgs( 1 ) {
39             # do some..
40             }
41            
42             # url would be "/one/*/two/*"
43             sub my_other : PathPart( 'two') : Chained( 'my_index' ) : Args( 1 ) {
44             # do some..
45             }
46              
47             You would use:
48              
49             # somewhere
50             # this would call: "/namespace/one/111/two/222"
51             $c->forward_to_chained( [ qw/ namespace two / ], [ "111", "222 ] );
52            
53             # same as above
54             $c->forward_to_chained( "namespace/two", [ "111", "222 ] );
55              
56              
57             =head2 Example 2
58              
59             it's not always obvious which path to choose when calling "forward_to_chained" ..
60              
61             An example testing controller
62              
63             package MyApp::Controller::Testing;
64            
65             use strict;
66             use warnings;
67            
68             use base qw/ Catalyst::Controller /;
69             use Data::Dumper;
70            
71             __PACKAGE__->config->{ namespace } = 'testing';
72            
73             sub one : PathPart( 'testing/one' ) : Chained( '/' ) : CaptureArgs( 1 ) {
74             my ( $self, $c, @args ) = @_;
75             push @{ $c->stash->{ called } ||= [] }, {
76             name => 'one',
77             args => \@args
78             };
79             }
80            
81             sub two : Chained( 'one' ) : CaptureArgs( 1 ) {
82             my ( $self, $c, @args ) = @_;
83             push @{ $c->stash->{ called } ||= [] }, {
84             name => 'two',
85             args => \@args
86             };
87             }
88            
89             sub three : Chained( 'two' ) {
90             my ( $self, $c, @args ) = @_;
91             push @{ $c->stash->{ called } ||= [] }, {
92             name => 'three',
93             args => \@args
94             };
95             }
96            
97            
98             sub right : PathPart( 'testing/right' ) : Chained( '/' ) : CaptureArgs( 0 ) {
99             my ( $self, $c, @args ) = @_;
100             push @{ $c->stash->{ called } ||= [] }, {
101             name => 'right',
102             args => \@args
103             };
104             }
105            
106             sub again : Chained( 'right' ) : Args( 1 ) {
107             my ( $self, $c, @args ) = @_;
108             push @{ $c->stash->{ called } ||= [] }, {
109             name => 'again',
110             args => \@args
111             };
112             }
113            
114            
115             sub chainor : Local {
116             my ( $self, $c ) = @_;
117            
118             # calling chained:
119            
120             # 1) WRONG:
121             #$c->forward_to_chained( 'testing/one/arg1/two/arg2/three/arg3' );
122            
123             # 2) WRONG:
124             #$c->forward_to_chained( 'testing/one/two/three', [ qw/ arg1 arg2 arg3 arg4 / ] );
125            
126             # 3) CORRECT:
127             $c->forward_to_chained( 'testing/three', [qw/ arg1 arg2 arg3 arg4 /] );
128            
129             $c->forward_to_chained( 'testing/again', [qw/ arg /] );
130            
131             $c->res->content_type( 'text/plain' );
132             $c->res->body( "Called: \n". Dumper( $c->stash->{ called } ) );
133             }
134            
135             1;
136              
137              
138             would produce something like this:
139              
140             Called:
141             $VAR1 = [
142             {
143             'args' => [
144             'arg1'
145             ],
146             'name' => 'one'
147             },
148             {
149             'args' => [
150             'arg2'
151             ],
152             'name' => 'two'
153             },
154             {
155             'args' => [
156             'arg3',
157             'arg4'
158             ],
159             'name' => 'three'
160             },
161             {
162             'args' => [],
163             'name' => 'right'
164             },
165             {
166             'args' => [
167             'arg'
168             ],
169             'name' => 'again'
170             }
171             ];
172              
173              
174             and catalyst debug out:
175              
176             .----------------------------------------------------------------+-----------.
177             | Action | Time |
178             +----------------------------------------------------------------+-----------+
179             | /begin | 0.064814s |
180             | /testing/chainor | 0.002931s |
181             | /testing/one | 0.000588s |
182             | /testing/two | 0.000208s |
183             | /testing/three | 0.000197s |
184             | /testing/right | 0.000061s |
185             | /testing/again | 0.000055s |
186             | /end | 0.000495s |
187             '----------------------------------------------------------------+-----------'
188              
189              
190             =head1 METHODS
191              
192             =cut
193              
194 1     1   1074 use strict;
  1         3  
  1         48  
195 1     1   6 use warnings;
  1         4  
  1         41  
196              
197 1     1   19 use vars qw/ $VERSION /;
  1         2  
  1         61  
198 1     1   564 use Catalyst::Exception;
  0            
  0            
199              
200             $VERSION = '0.03';
201              
202              
203             =head2 forward_to_chained
204              
205             forwards to a certain chained action endpoint ..
206              
207             $c->forward_to_chained( "some/path", [ qw/ arg1 arg2 arg3 / ] );
208             $c->forward_to_chained( [qw/ some path /], [ qw/ arg1 arg2 arg3 / ] );
209              
210             =cut
211              
212             sub forward_to_chained {
213             my ( $c, $chained_ref, $args_ref ) = @_;
214            
215            
216             # transform from string to array-ref .. and back to clear things
217             $chained_ref = [ grep { length } split( /\//, $chained_ref ) ]
218             unless ref( $chained_ref );
219             my $search_chain = join( "/", @{ $chained_ref } );
220            
221             # search chain parts in action hash ..
222             my $actions_ref = $c->dispatcher->action_hash;
223             my ( @chain, %seen ) = ();
224            
225             # while defined the action path in the action ref... cycle through url
226             SEARCH_CHAIN:
227             while ( defined( my $action_ref = $actions_ref->{ $search_chain } ) && !$seen{ $search_chain }++ ) {
228            
229             # building our chain..
230             unshift @chain, $action_ref;
231            
232             # found next part ...
233             if ( defined $action_ref->{ attributes }->{ Chained } ) {
234             $search_chain = $action_ref->{ attributes }->{ Chained }->[ -1 ]; # current part of "url"
235             $search_chain =~ s~^\/+~~; # remove any leading "/"
236             }
237            
238             # not further parts
239             else {
240             last SEARCH_CHAIN;
241             }
242             }
243            
244             # no chain found: bye bye
245             Catalyst::Exception->throw(
246             message => "Cant forward to chained action because cant find chain for '$search_chain'" )
247             if ( scalar @chain == 0 );
248            
249            
250             # going to build up / setup new action.. and dispatch to this action
251            
252             # save orig captures ..
253             my $captures_ref = $c->req->captures;
254            
255             # .. setup new captures ..
256             $args_ref ||= [];
257             $args_ref = [ $args_ref ] unless ref( $args_ref );
258             $c->req->captures( $args_ref );
259            
260             # .. build up action chain and settle to catalyst ..
261             my $action_chain = __Catalyst_ActionChain->from_chain( \@chain );
262             #$c->action( Catalyst::ActionChain->from_chain( \@chain ) );
263            
264             # .. dispatch to it ..
265             $action_chain->dispatch( $c );
266             #$c->dispatcher->dispatch( $c );
267            
268             # .. and set orig captures back
269             $c->req->captures( $captures_ref );
270            
271             return ;
272             }
273              
274              
275              
276             =head2 get_chained_action_endpoints
277              
278             returns array or arrayref of endpoints.. to help you find the one you need
279              
280             my @endpoints = $c->get_chained_action_endpoints;
281             my $endpoints_ref = $c->get_chained_action_endpoints;
282              
283             =cut
284              
285             sub get_chained_action_endpoints {
286             my ( $c ) = @_;
287            
288             my $actions_ref = $c->dispatcher->action_hash;
289             my @endpoints =
290             sort
291             grep {
292             defined $actions_ref->{ $_ }->{ attributes } &&
293             ref $actions_ref->{ $_ }->{ attributes }->{ Chained }
294             }
295             grep { ! /(?:^|\/)_[A-Z]+$/ } keys %{ $actions_ref }
296             ;
297            
298             return wantarray ? @endpoints : \@endpoints;
299             }
300              
301              
302              
303              
304              
305              
306              
307             =head1 AUTHOR
308              
309             Ulrich Kautz, uk@fortrabbit.de
310              
311             =cut
312              
313              
314             1;
315              
316             #
317             # we require some small changes on the Catalyst::ActionChain::dispatch-method
318             # to provide the request-arguments to the last chain-action ..
319             #
320              
321              
322             package __Catalyst_ActionChain;
323              
324             use strict;
325             use base qw/ Catalyst::ActionChain /;
326              
327             sub dispatch {
328             my ( $self, $c ) = @_;
329             my @captures = @{$c->req->captures||[]};
330             my @chain = @{ $self->chain };
331             my $last = pop(@chain);
332             foreach my $action ( @chain ) {
333             my @args;
334             if (my $cap = $action->attributes->{CaptureArgs}) {
335             @args = splice(@captures, 0, $cap->[0]);
336             }
337             local $c->request->{arguments} = \@args;
338             $action->dispatch( $c );
339             }
340            
341             # --- START CHANGES ----
342             my @args;
343             if ( my $cap = $last->attributes->{Args} ) {
344             @args = $#$cap > -1
345             ? splice(@captures, 0, $cap->[0])
346             : @captures
347             ;
348             }
349             local $c->request->{arguments} = \@args;
350             # --- END CHANGES ----
351            
352             $last->dispatch( $c );
353             }
354              
355             1;