File Coverage

lib/CatalystX/Resource/Controller/Resource.pm
Criterion Covered Total %
statement 91 93 97.8
branch 54 70 77.1
condition 5 9 55.5
subroutine 12 12 100.0
pod 2 2 100.0
total 164 186 88.1


line stmt bran cond sub pod time code
1             package CatalystX::Resource::Controller::Resource;
2             $CatalystX::Resource::Controller::Resource::VERSION = '0.03';
3 9     9   6858931 use Moose;
  9         27  
  9         57  
4 9     9   58781 use namespace::autoclean;
  9         21  
  9         72  
5              
6             # ABSTRACT: Base Controller for Resources
7              
8 9     9   740 BEGIN { extends 'Catalyst::Controller::ActionRole'; }
9              
10 9     9   896617 use MooseX::Types::Moose qw/ ArrayRef /;
  9         27  
  9         76  
11 9     9   51484 use MooseX::Types::Common::String qw/ NonEmptySimpleStr /;
  9         758318  
  9         76  
12              
13             with qw/
14             CatalystX::Component::Traits
15             /;
16              
17             # merge traits from app config with local traits
18             has '+_trait_merge' => (default => 1);
19              
20             __PACKAGE__->config(
21             traits => [qw/
22             List
23             Show
24             Delete
25             Form
26             Create
27             Edit
28             /],
29             );
30              
31              
32             has 'model' => (
33             is => 'ro',
34             isa => NonEmptySimpleStr,
35             required => 1,
36             );
37              
38              
39             has 'identifier_candidates' => (
40             is => 'ro',
41             isa => 'ArrayRef',
42             default => sub {[ qw/ name title / ]},
43             );
44              
45              
46             has 'resultset_key' => (
47             is => 'ro',
48             isa => NonEmptySimpleStr,
49             required => 1,
50             );
51              
52              
53             has 'resource_key' => (
54             is => 'ro',
55             isa => NonEmptySimpleStr,
56             required => 1,
57             );
58              
59              
60             has 'parent_key' => (
61             is => 'ro',
62             isa => NonEmptySimpleStr,
63             predicate => 'has_parent',
64             );
65              
66              
67             has 'parents_accessor' => (
68             is => 'ro',
69             isa => NonEmptySimpleStr,
70             );
71              
72              
73             has 'prefetch' => (
74             is => 'ro',
75             predicate => 'has_prefetch',
76             );
77              
78              
79             has 'redirect_mode' => (
80             is => 'rw',
81             isa => NonEmptySimpleStr,
82             default => 'list',
83             );
84              
85              
86             has 'error_path' => (
87             is => 'ro',
88             isa => NonEmptySimpleStr,
89             default => '/default',
90             );
91              
92              
93             sub _redirect {
94 34     34   145 my ( $self, $c ) = @_;
95              
96 34         85 my $path;
97 34         1354 my $mode = $self->redirect_mode;
98 34         93 my @captures = @{ $c->request->captures };
  34         804  
99 34         2191 my $action = $c->action->name;
100              
101             ########################
102             # redirect_mode 'list' #
103             ########################
104             # path: /parents/1/resources/create => redirect_path: /parents/1/resources/list
105             # path: /parents/1/resources/3/edit => redirect_path: /parents/1/resources/list
106             # path: /parents/1/resources/3/delete => redirect_path: /parents/1/resources/list
107 34 100       1592 if ( $mode eq 'list' ) {
    100          
    100          
    50          
108 16 100       75 pop(@captures)
109             unless $action eq 'create';
110 16         159 $path = $c->uri_for_action($self->action_for('list'), \@captures);
111             }
112              
113             ########################
114             # redirect_mode 'show' #
115             ########################
116             # path: /parents/1/resources/create => redirect_path: /parents/1/resources/<id>/show
117             # path: /parents/1/resources/3/edit => redirect_path: /parents/1/resources/3/show
118             # path: /parents/1/resources/3/delete => redirect_path: /parents/1/resources/list
119             elsif ( $mode eq 'show' ) {
120 6 100       35 if ( $action eq 'create' ) {
    100          
    50          
121 2         9 my $id_of_created_resource = $c->stash->{ $self->resource_key }->id;
122 2         31 push @captures, $id_of_created_resource;
123 2         37 $path = $c->uri_for_action($self->action_for('show'), \@captures);
124             }
125             elsif ( $action eq 'delete' ) {
126 2         6 pop(@captures);
127 2         20 $path = $c->uri_for_action($self->action_for('list'), \@captures);
128             }
129             elsif ( $action eq 'edit' ) {
130 2         19 $path = $c->uri_for_action($self->action_for('show'), \@captures);
131             }
132             }
133              
134             ###############################
135             # redirect_mode 'show_parent' #
136             ###############################
137             # path: /parents/1/resources/create => redirect_path: /parents/1/show
138             # path: /resources/create => redirect_path: /resources/list
139             # path: /parents/1/resources/3/edit => redirect_path: /parents/1/show
140             # path: /resources/3/edit => redirect_path: /resources/list
141             # path: /parents/1/resources/3/delete => redirect_path: /parents/1/show
142             # path: /resources/3/delete => redirect_path: /resources/list
143             elsif ( $mode eq 'show_parent' ) {
144 6 100       242 if ( $self->has_parent ) {
145 3         10 my @chain = @{ $c->dispatcher->expand_action( $c->action )->{chain} };
  3         35  
146              
147             # base_with_id action of parent
148 3         2382 my $parent_base_with_id_action;
149 3 100 66     34 if ($action eq 'create') {
    50          
150 1         4 $parent_base_with_id_action = $chain[-3];
151             } elsif ($action eq 'edit'
152             || $action eq 'delete'
153             ) {
154 2         6 $parent_base_with_id_action = $chain[-4];
155 2         6 pop @captures;
156             }
157              
158             # parent namespace
159 3         15 my $parent_namespace = $parent_base_with_id_action->{namespace};
160              
161             # private path of show action of parent
162 3         11 my $parent_show_action_private_path = "$parent_namespace/show";
163 3         21 $path = $c->uri_for_action($parent_show_action_private_path, \@captures);
164             }
165             else {
166 3         32 $path = $c->uri_for_action($self->action_for('list'));
167             }
168             }
169              
170             ####################################
171             # redirect_mode 'show_parent_list' #
172             ####################################
173             # path: /parents/1/resources/create => redirect_path: /parents/list
174             # path: /parents/1/resources/3/edit => redirect_path: /parents/list
175             # path: /parents/1/resources/3/delete => redirect_path: /parents/list
176             elsif ( $mode eq 'show_parent_list' ) {
177 6 100       240 if ( $self->has_parent ) {
178 3         10 my @chain = @{ $c->dispatcher->expand_action( $c->action )->{chain} };
  3         20  
179              
180             # base action of parent
181 3         2457 my $parent_base_action;
182 3 100 66     26 if ($action eq 'create') {
    50          
183 1         3 $parent_base_action = $chain[-4];
184 1         3 pop @captures;
185             } elsif ($action eq 'edit' || $action eq 'delete') {
186 2         6 $parent_base_action = $chain[-5];
187 2         6 pop @captures;
188 2         6 pop @captures;
189             }
190              
191             # parent namespace
192 3         14 my $parent_namespace = $parent_base_action->{namespace};
193              
194             # private path of list action of parent
195 3         10 my $parent_list_action_private_path = "$parent_namespace/list";
196 3         21 $path = $c->uri_for_action($parent_list_action_private_path, \@captures);
197             }
198             else {
199 3         35 $path = $c->uri_for_action($self->action_for('list'));
200             }
201             }
202              
203 34         99972 $c->res->redirect($path);
204             }
205              
206              
207             sub _msg {
208 54     54   268 my ( $self, $c, $action, $id ) = @_;
209              
210 54 100       438 if ( $action eq 'not_found' ) {
    100          
    100          
    100          
    100          
    100          
    100          
    50          
211 12 50       153 return $c->can('loc')
212             ? $c->loc( 'error.resource_not_found', $id )
213             : "No such resource: $id";
214             }
215             elsif ( $action eq 'create' ) {
216 11 50       250 return $c->can('loc')
217             ? $c->loc( 'resources.created', $self->_identifier($c) )
218             : $self->_identifier($c) . " created.";
219             }
220             elsif ( $action eq 'update' ) {
221 13 50       180 return $c->can('loc')
222             ? $c->loc( 'resources.updated', $self->_identifier($c) )
223             : $self->_identifier($c) . " updated.";
224             }
225             elsif ( $action eq 'delete' ) {
226 10 50       143 return $c->can('loc')
227             ? $c->loc( 'resources.deleted', $self->_identifier($c) )
228             : $self->_identifier($c) . " deleted.";
229             }
230             elsif ( $action eq 'move_next' ) {
231 4 50       64 return $c->can('loc')
232             ? $c->loc( 'resources.moved_next', $self->_identifier($c) )
233             : $self->_identifier($c) . " moved next.";
234             }
235             elsif ( $action eq 'move_previous' ) {
236 2 50       21 return $c->can('loc')
237             ? $c->loc( 'resources.moved_previous', $self->_identifier($c) )
238             : $self->_identifier($c) . " moved previous.";
239             }
240             elsif ( $action eq 'move_to' ) {
241 1 50       10 return $c->can('loc')
242             ? $c->loc( 'resources.moved_to', $self->_identifier($c) )
243             : $self->_identifier($c) . " moved.";
244             }
245             elsif ( $action eq 'move_to_undef' ) {
246 1 50       10 return $c->can('loc')
247             ? $c->loc( 'resources.move_to_undef', $self->_identifier($c) )
248             : 'Could not move ' . $self->_identifier($c) . '. No position defined.';
249             }
250             }
251              
252              
253             sub _identifier {
254 42     42   162 my ( $self, $c ) = @_;
255 42         160 my $resource = $c->stash->{ $self->resource_key };
256              
257 42         121 for my $identifier (@{ $self->identifier_candidates }) {
  42         1647  
258 42 50 33     1116 if (
259             $resource->can( $identifier )
260             && defined $resource->$identifier
261             ) {
262 42         1631 return $resource->$identifier
263             }
264             }
265              
266 0 0         my $identifier =
267             $resource->can('id')
268             ? $self->resource_key . ' (id: ' . $resource->id . ')'
269             : $self->resource_key;
270 0           return ucfirst( $identifier );
271             }
272              
273              
274             sub base : Chained('') PathPart('') CaptureArgs(0) {
275 184     184 1 10602582 my ( $self, $c ) = @_;
276              
277             # Store the ResultSet in stash so it's available for other methods
278             # get the model from the controllers config that consumes this role
279 184         489 my $resultset;
280              
281 184 100       7417 if ( $self->has_parent ) {
282 62         285 $resultset = $c->stash->{ $self->parent_key }
283             ->related_resultset( $self->parents_accessor );
284             }
285             else {
286 122         4213 $resultset = $c->model( $self->model );
287             }
288              
289 184 100       114890 $resultset = $resultset->search_rs( undef, { prefetch => $self->prefetch } )
290             if $self->has_prefetch;
291              
292 184         49461 $c->stash( $self->resultset_key => $resultset );
293 9     9   38518 }
  9         26  
  9         126  
294              
295              
296             sub base_with_id : Chained('base') PathPart('') CaptureArgs(1) {
297 133     133 1 74304 my ( $self, $c, $id ) = @_;
298 133         595 my $resource = $c->stash->{ $self->resultset_key }->find($id);
299 133 100       1390153 if ($resource) {
300 121         744 $c->stash->{ $self->resource_key } = $resource;
301             }
302             else {
303 12         131 $c->stash( error_msg => $self->_msg( $c, 'not_found', $id ) );
304 12         1678 $c->detach( $self->error_path );
305             }
306 9     9   14574 }
  9         22  
  9         46  
307              
308             __PACKAGE__->meta->make_immutable();
309             1;
310              
311             __END__
312              
313             =pod
314              
315             =encoding UTF-8
316              
317             =head1 NAME
318              
319             CatalystX::Resource::Controller::Resource - Base Controller for Resources
320              
321             =head1 VERSION
322              
323             version 0.03
324              
325             =head1 ATTRIBUTES
326              
327             =head2 model
328              
329             required, the DBIC model associated with this resource. (e.g.: 'DB::CDs')
330              
331             =head2 identifier_candidates
332              
333             ArrayRef of column names used as name in messages.
334              
335             if you edit, delete, ... a resource a msg is stored in the stash.
336             the first candidate available as accessor on the resoure (tested with
337             $row->can(...)) that returns a defined value will be used.
338              
339             example: "'Michael Jackson' has been deleted.", "'Artist (id: 3)' has been updated."
340              
341             default: [ 'name', 'title' ]
342              
343             if no identifier is found resource_key is used
344              
345             =head2 resultset_key
346              
347             stash key used to store the resultset of this resource. (e.g.: 'albums')
348              
349             =head2 resource_key
350              
351             stash key used to store specific result of this resource. (e.g.: 'album')
352             You will need this to access your resource in your template.
353              
354             =head2 parent_key
355              
356             for a nested resource 'parent_key' is used as stash key to store the parent item
357             (e.g.: 'artist')
358             this is required if parent_key is set
359              
360             =head2 parents_accessor
361              
362             the accessor on the parent resource to get a resultset
363             of this resource (accessor in DBIC has_many)
364             (e.g.: 'albums')
365             this is required if parent_key is set
366              
367             =head2 prefetch
368              
369             The prefetch attribute value is passed through. See L<DBIx::Class::ResultSet> for details.
370             (e.g.: 'tracks', [qw/tracks credits/])
371              
372             =head2 redirect_mode list|show|show_parent|show_parent_list
373              
374             After a created/edit/delete action a redirect takes place.
375             The redirect behavior can be controlled with the redirect_mode attribute.
376              
377             default = 'list'
378              
379             =head2 error_path
380              
381             documented in L<CatalystX::Resource>
382              
383             =head1 METHODS
384              
385             =head2 _redirect
386              
387             redirect request after create/edit/delete
388              
389             =head2 _msg
390              
391             returns notification msg to be displayed
392              
393             =head2 _identifier
394              
395             return an identifier for the resource
396              
397             =head1 ACTIONS
398              
399             the following actions will be loaded
400              
401             =head2 base
402              
403             Starts a chain and puts resultset into stash
404              
405             For nested resources chain childrens 'base' action
406             to parents 'base_with_id' action
407              
408             =head2 base_with_id
409              
410             chains to 'base' and puts resource with id into stash
411              
412             =head1 AUTHOR
413              
414             David Schmidt <davewood@cpan.org>
415              
416             =head1 COPYRIGHT AND LICENSE
417              
418             This software is copyright (c) 2011 by David Schmidt.
419              
420             This is free software; you can redistribute it and/or modify it under
421             the same terms as the Perl 5 programming language system itself.
422              
423             =cut