File Coverage

blib/lib/CatalystX/CRUD/Controller.pm
Criterion Covered Total %
statement 207 301 68.7
branch 46 112 41.0
condition 23 50 46.0
subroutine 53 64 82.8
pod 31 31 100.0
total 360 558 64.5


line stmt bran cond sub pod time code
1             package CatalystX::CRUD::Controller;
2 6     6   2717 use Moose;
  6         476637  
  6         49  
3              
4             BEGIN {
5 6     6   43242 extends qw(
6             CatalystX::CRUD
7             Catalyst::Controller
8             );
9             }
10 6     6   606139 use Carp;
  6         14  
  6         469  
11 6     6   57 use Catalyst::Utils;
  6         15  
  6         203  
12 6     6   3099 use CatalystX::CRUD::Results;
  6         22  
  6         254  
13 6     6   42 use MRO::Compat;
  6         13  
  6         141  
14 6     6   31 use mro 'c3';
  6         15  
  6         51  
15 6     6   177 use Data::Dump qw( dump );
  6         109  
  6         310  
16 6     6   46 use Try::Tiny;
  6         16  
  6         1222  
17              
18             __PACKAGE__->mk_accessors(
19             qw(
20             model_adapter
21             form_class
22             init_form
23             init_object
24             model_name
25             model_meta
26             default_template
27             primary_key
28             allow_GET_writes
29             naked_results
30             page_size
31             view_on_single_result
32             )
33             );
34              
35             __PACKAGE__->config(
36             primary_key => 'id',
37             view_on_single_result => 0,
38             page_size => 50,
39             allow_GET_writes => 0,
40             naked_results => 0,
41             );
42              
43             # apply Role *after* we declare accessors above
44             with 'CatalystX::CRUD::ControllerRole';
45              
46             our $VERSION = '0.58';
47              
48             =head1 NAME
49              
50             CatalystX::CRUD::Controller - base class for CRUD controllers
51              
52             =head1 SYNOPSIS
53              
54             # create a controller
55             package MyApp::Controller::Foo;
56             use strict;
57             use base qw( CatalystX::CRUD::Controller );
58            
59             __PACKAGE__->config(
60             form_class => 'MyForm::Foo',
61             init_form => 'init_with_foo',
62             init_object => 'foo_from_form',
63             default_template => 'path/to/foo/edit.tt',
64             model_name => 'Foo',
65             model_adapter => 'FooAdapter', # optional
66             model_meta => { moniker => 'SomeTable' }, # optional
67             primary_key => 'id',
68             view_on_single_result => 0,
69             page_size => 50,
70             allow_GET_writes => 0,
71             naked_results => 0,
72             );
73            
74             1;
75            
76             # now you can manage Foo objects using your MyForm::Foo form class
77             # with URIs at:
78             # foo/<pk>/edit
79             # foo/<pk>/view
80             # foo/<pk>/save
81             # foo/<pk>/rm
82             # foo/<pk>/<relname>/<pk2>/add
83             # foo/<pk>/<relname>/<pk2>/rm
84             # foo/create
85             # foo/list
86             # foo/search
87            
88             =head1 DESCRIPTION
89              
90             CatalystX::CRUD::Controller is a base class for writing controllers that
91             play nicely with the CatalystX::CRUD::Model API. The basic controller API
92             is based on Catalyst::Controller::Rose::CRUD and Catalyst::Controller::Rose::Search.
93              
94             See CatalystX::CRUD::Controller::RHTMLO for one implementation.
95              
96             =head1 CONFIGURATION
97              
98             See the L<SYNOPSIS> section.
99              
100             The configuration values are used extensively in the methods
101             described below and are noted B<in bold> where they are used.
102              
103             =head1 URI METHODS
104              
105             The following methods are either public via the default URI namespace or
106             (as with auto() and fetch()) are called via the dispatch chain. See the L<SYNOPSIS>.
107              
108             =head2 auto
109              
110             Attribute: Private
111              
112             Calls the form() method and saves the return value in stash() as C<form>.
113              
114             =cut
115              
116             sub auto : Private {
117 55     55 1 1137510 my ( $self, $c, @args ) = @_;
118 55         268 $c->stash->{form} = $self->form($c);
119 55         4189 $self->maybe::next::method( $c, @args );
120 55         903 1;
121 6     6   50 }
  6         14  
  6         139  
122              
123             =head2 default
124              
125             Attribute: Private
126              
127             The fallback method. The default returns a 404 error.
128              
129             =cut
130              
131             sub default : Path {
132 1     1 1 1207 my ( $self, $c, @args ) = @_;
133 1         4 $c->res->body('Not found');
134 1         66 $c->res->status(404);
135 6     6   9417 }
  6         20  
  6         32  
136              
137             =head2 fetch( I<primary_key> )
138              
139             Attribute: chained to namespace, expecting one argument.
140              
141             Calls B<do_model> fetch() method with a single key/value pair,
142             using the B<primary_key> config value as the key and the I<primary_key> as the value.
143              
144             The return value of fetch() is saved in stash() as C<object>.
145              
146             The I<primary_key> value is saved in stash() as C<object_id>.
147              
148             =cut
149              
150             sub fetch : Chained('/') PathPrefix CaptureArgs(1) {
151 35     35 1 34366 my ( $self, $c, $id ) = @_;
152 35         120 $c->stash->{object_id} = $id;
153 35         2708 my @pk = $self->get_primary_key( $c, $id );
154              
155             # make sure all elements of the @pk pairs are not-null
156 35 50       131 if ( scalar(@pk) % 2 ) {
157 0         0 $self->throw_error(
158             "Odd number of elements returned from get_primary_key()");
159             }
160 35         141 my %pk_pairs = @pk;
161 35         52 my $pk_is_null;
162 35         95 for my $key ( keys %pk_pairs ) {
163 35         59 my $val = $pk_pairs{$key};
164 35 100 66     205 if ( !defined($val) or !length($val) ) {
165 1         2 $pk_is_null = $key;
166 1         2 last;
167             }
168             }
169 35 50 33     138 if ( $c->debug and defined $pk_is_null ) {
170 0         0 $c->log->debug("Null PK value for '$pk_is_null'");
171             }
172 35 100 66     292 my @arg = ( defined $pk_is_null || !$id ) ? () : (@pk);
173 35 50       97 $c->log->debug( "fetch: " . dump \@arg ) if $c->debug;
174              
175             try {
176 35     35   1730 $c->stash->{object} = $self->do_model( $c, 'fetch', @arg );
177 34 100 66     4512 if ( $self->has_errors($c) or !$c->stash->{object} ) {
178 12         1852 $self->throw_error( 'No such ' . $self->model_name );
179             }
180             }
181             catch {
182 13     13   838 $c->res->status(404);
183 13         2103 $c->res->body( 'No such ' . $self->model_name );
184              
185             # re-throw so we interrupt chain.
186 13         2564 $self->throw_error($_);
187 35         382 };
188 6     6   8154 }
  6         45  
  6         39  
189              
190             =head2 create
191              
192             Attribute: Local
193              
194             Namespace for creating a new object. Calls to fetch() and edit()
195             with a B<primary_key> value of C<0> (zero).
196              
197             If the Form class has a 'field_value' method, create() will
198             pre-populate the Form instance and Object instance
199             with param-based values (i.e. seeds the form via request params).
200              
201             Example:
202              
203             http://localhost/foo/create?name=bar
204             # form and object will have name set to 'bar'
205              
206             B<NOTE:> This is a GET method named for consistency with the C
207             in CRUD. It is not equivalent to a POST in REST terminology.
208              
209             =cut
210              
211             sub create : Path('create') {
212 0     0 1 0 my ( $self, $c ) = @_;
213 0         0 $self->fetch( $c, 0 );
214              
215             # allow for params to be passed in to seed the form/object
216 0         0 my $form = $c->stash->{form};
217 0         0 my $obj = $c->stash->{object};
218 0 0       0 if ( $form->can('field_value') ) {
219 0         0 for my $field ( $self->field_names($c) ) {
220 0 0       0 $c->log->debug("checking for param: $field") if $c->debug;
221 0 0       0 if ( exists $c->req->params->{$field} ) {
222 0 0       0 $c->log->debug("setting form param: $field") if $c->debug;
223 0         0 $form->field_value( $field => $c->req->params->{$field} );
224 0 0       0 if ( $obj->can($field) ) {
225 0 0       0 $c->log->debug("setting object method: $field")
226             if $c->debug;
227 0         0 $obj->$field( $c->req->params->{$field} );
228             }
229             }
230             }
231             }
232              
233 0         0 $self->edit($c);
234              
235 6     6   7339 }
  6         29  
  6         41  
236              
237             =head2 edit
238              
239             Attribute: chained to fetch(), expecting no arguments.
240              
241             Checks the can_read() and has_errors() methods before proceeding.
242              
243             Populates the C<form> in stash() with the C<object> in stash(),
244             using the B<init_form> method. Sets the C<template> value in stash()
245             to B<default_template>.
246              
247             =cut
248              
249             sub edit : PathPart Chained('fetch') Args(0) {
250 0     0 1 0 my ( $self, $c ) = @_;
251 0 0       0 return if $self->has_errors($c);
252 0 0       0 unless ( $self->can_read($c) ) {
253 0         0 $self->throw_error('Permission denied');
254 0         0 return;
255             }
256 0         0 my $meth = $self->init_form;
257 0         0 $c->stash->{form}->$meth( $c->stash->{object} );
258              
259             # might get here from create()
260 0         0 $c->stash->{template} = $self->default_template;
261 6     6   6565 }
  6         35  
  6         30  
262              
263             =head2 view
264              
265             Attribute: chained to fetch(), expecting no arguments.
266              
267             Checks the can_read() and has_errors() methods before proceeding.
268              
269             Acts the same as edit() but does not set template value in stash().
270              
271             =cut
272              
273             sub view : PathPart Chained('fetch') Args(0) {
274 12     12 1 5018 my ( $self, $c ) = @_;
275 12 50       44 return if $self->has_errors($c);
276 12 50       1065 unless ( $self->can_read($c) ) {
277 0         0 $self->throw_error('Permission denied');
278 0         0 return;
279             }
280 12         51 my $meth = $self->init_form;
281 12         1595 $c->stash->{form}->$meth( $c->stash->{object} );
282 6     6   6474 }
  6         26  
  6         30  
283              
284             =head2 read
285              
286             Alias for view(), just for consistency with the R in CRUD.
287              
288             =cut
289              
290             sub read : PathPart Chained('fetch') Args(0) {
291 0     0 1 0 my ( $self, $c ) = @_;
292 0         0 $self->view($c);
293 6     6   6636 }
  6         11  
  6         44  
294              
295             =head2 save
296              
297             Attribute: chained to fetch(), expecting no arguments.
298              
299             Creates an object with form_to_object(), then follows the precommit(),
300             save_obj() and postcommit() logic.
301              
302             See the save_obj(), precommit() and postcommit() hook methods for
303             ways to affect the behaviour of save().
304              
305             The special param() value C<_delete> is checked to support POST requests
306             to /save. If found, save() will detach() to rm().
307              
308             save() returns 0 on any error, and returns 1 on success.
309              
310             =cut
311              
312             sub save : PathPart Chained('fetch') Args(0) {
313 9     9 1 5044 my ( $self, $c ) = @_;
314              
315 9         50 $self->_check_idempotent($c);
316              
317 9 50 33     781 if ($c->request->params->{'_delete'}
      33        
318             or ( exists $c->request->params->{'x-tunneled-method'}
319             and $c->request->params->{'x-tunneled-method'} eq 'DELETE' )
320             )
321             {
322 0         0 $c->action->name('rm'); # so we can test against it in postcommit()
323 0         0 $self->rm($c);
324 0         0 return;
325             }
326              
327 9 50       1344 return if $self->has_errors($c);
328 9 50       766 unless ( $self->can_write($c) ) {
329 0         0 $self->throw_error('Permission denied');
330 0         0 return;
331             }
332              
333             # get a valid object
334 9         55 my $obj = $self->form_to_object($c);
335 9 50       32 if ( !$obj ) {
336 0 0       0 $c->log->debug("form_to_object() returned false") if $c->debug;
337 0         0 return 0;
338             }
339              
340             # write our changes
341 9 50       112 unless ( $self->precommit( $c, $obj ) ) {
342 0   0     0 $c->stash->{template} ||= $self->default_template;
343 0         0 return 0;
344             }
345 9         48 $self->save_obj( $c, $obj );
346 9         755 $self->postcommit( $c, $obj );
347              
348 9         37 1;
349 6     6   7468 }
  6         17  
  6         42  
350              
351             =head2 update
352              
353             Alias for save(), just for consistency with the U in CRUD.
354              
355             =cut
356              
357             sub update : PathPart Chained('fetch') Args(0) {
358 0     0 1 0 my ( $self, $c ) = @_;
359 0         0 $self->save($c);
360 6     6   6278 }
  6         26  
  6         37  
361              
362             =head2 rm
363              
364             Attribute: chained to fetch(), expecting no arguments.
365              
366             Checks the can_write() and has_errors() methods before proceeeding.
367              
368             Calls the delete() method on the C<object>.
369              
370             =cut
371              
372             sub rm : PathPart Chained('fetch') Args(0) {
373 7     7 1 1623 my ( $self, $c ) = @_;
374 7         23 $self->_check_idempotent($c);
375 7 50       445 return if $self->has_errors($c);
376 7 50       580 unless ( $self->can_write($c) ) {
377 0         0 $self->throw_error('Permission denied');
378 0         0 return;
379             }
380              
381 7         24 my $o = $c->stash->{object};
382              
383 7 50       472 unless ( $self->precommit( $c, $o ) ) {
384 0         0 return 0;
385             }
386 7 100       23 if ( $self->model_adapter ) {
387 2         257 $self->model_adapter->delete( $c, $o );
388             }
389             else {
390 5         631 $o->delete;
391             }
392 7         1092 $self->postcommit( $c, $o );
393 6     6   6734 }
  6         14  
  6         27  
394              
395             =head2 delete
396              
397             Wrapper for rm(), just for consistency with the D in CRUD.
398              
399             =cut
400              
401             sub delete : PathPart Chained('fetch') Args(0) {
402 4     4 1 1696 my ( $self, $c ) = @_;
403 4         28 $self->rm($c);
404 6     6   6195 }
  6         15  
  6         33  
405              
406             =head2 list
407              
408             Attribute: Local
409              
410             Display all the objects represented by model_name().
411             The same as calling search() with no params().
412             See do_search().
413              
414             =cut
415              
416             sub list : Local {
417 4     4 1 16 my ( $self, $c, @arg ) = @_;
418 4 50       15 unless ( $self->can_read($c) ) {
419 0         0 $self->throw_error('Permission denied');
420 0         0 return;
421             }
422              
423 4         30 $self->do_search( $c, @arg );
424 6     6   6318 }
  6         13  
  6         45  
425              
426             =head2 search
427              
428             Attribute: Local
429              
430             Query the model and return results. See do_search().
431              
432             =cut
433              
434             sub search : Local {
435 8     8 1 9442 my ( $self, $c, @arg ) = @_;
436 8 50       64 unless ( $self->can_read($c) ) {
437 0         0 $self->throw_error('Permission denied');
438 0         0 return;
439             }
440              
441 8         36 $self->do_search( $c, @arg );
442 6     6   6146 }
  6         16  
  6         32  
443              
444             =head2 count
445              
446             Attribute: Local
447              
448             Like search() but does not set result values, only a total count.
449             Useful for AJAX-y types of situations where you want to query for a total
450             number of matches and create a pager but not actually retrieve any data.
451              
452             =cut
453              
454             sub count : Local {
455 0     0 1 0 my ( $self, $c, @arg ) = @_;
456 0 0       0 unless ( $self->can_read($c) ) {
457 0         0 $self->throw_error('Permission denied');
458 0         0 return;
459             }
460              
461 0         0 $c->stash->{fetch_no_results} = 1;
462              
463 0         0 $self->do_search( $c, @arg );
464 6     6   6321 }
  6         23  
  6         30  
465              
466             =head2 related( I<rel_name>, I<foreign_pk_value> )
467              
468             Attribute: chained to fetch(), expecting two arguments.
469              
470             Similar to fetch(), a chain base method for add_related()
471             and rm_related(). Expects two arguments: I<rel_name>
472             and I<foreign_pk_value>. Those two values are put in
473             stash under those key names.
474              
475             Note that related() has a PathPart of '' so it does
476             not appear in your URL:
477              
478             http://yourhost/foo/123/bars/456/add
479              
480             will resolve in the action_for add().
481              
482             =cut
483              
484             sub related : PathPart('') Chained('fetch') CaptureArgs(2) {
485 6     6 1 1533 my ( $self, $c, $rel, $fpk_value ) = @_;
486 6 50       22 return if $self->has_errors($c);
487 6 50       489 unless ( $self->can_write($c) ) {
488 0         0 $self->throw_error('Permission denied');
489 0         0 return;
490             }
491 6         17 $c->stash( rel_name => $rel );
492 6         470 $c->stash( foreign_pk_value => $fpk_value );
493 6     6   6298 }
  6         17  
  6         36  
494              
495             =head2 remove
496              
497             Attribute: chained to related().
498              
499             Dissociate a related many-to-many object of
500             relationship name I<rel_name> with primary key value I<foreign_pk_value>.
501              
502             Example:
503              
504             http://yoururl/user/123/group/456/remove
505              
506             will remove user C<123> from the group C<456>.
507              
508             Sets the 204 (enacted, no content) HTTP response status
509             on success.
510              
511             =cut
512              
513             sub _check_idempotent {
514 22     22   53 my ( $self, $c ) = @_;
515 22 50       86 if ( !$self->allow_GET_writes ) {
516 22 50       3094 if ( uc( $c->req->method ) eq 'GET' ) {
517 0         0 $c->log->warn( "allow_GET_writes!=true, related method="
518             . uc( $c->req->method ) );
519 0         0 $c->res->status(405);
520 0         0 $c->res->header( 'Allow' => 'POST,PUT,DELETE' );
521 0         0 $c->res->body('GET request not allowed');
522 0         0 $c->stash->{error} = 1; # so has_errors() will return true
523 0         0 return;
524             }
525             }
526             }
527              
528             sub remove : PathPart Chained('related') Args(0) {
529 3     3 1 847 my ( $self, $c ) = @_;
530 3         16 $self->_check_idempotent($c);
531 3 50       204 return if $self->has_errors($c);
532             $self->do_model(
533             $c, 'rm_related',
534             $c->stash->{object},
535             $c->stash->{rel_name},
536             $c->stash->{foreign_pk_value}
537 3         260 );
538 3         99 $c->res->status(204); # enacted, no content
539 6     6   7181 }
  6         22  
  6         35  
540              
541             =head2 add
542              
543             Attribute: chained to related().
544              
545             Associate the primary object retrieved in fetch() with
546             the object with I<foreign_pk_value>
547             via a related many-to-many relationship I<rel_name>.
548              
549             Example:
550              
551             http://yoururl/user/123/group/456/add
552              
553             will add user C<123> to the group C<456>.
554              
555             Sets the 204 (enacted, no content) HTTP response status
556             on success.
557              
558             =cut
559              
560             sub add : PathPart Chained('related') Args(0) {
561 3     3 1 877 my ( $self, $c ) = @_;
562 3         15 $self->_check_idempotent($c);
563 3 50       203 return if $self->has_errors($c);
564             $self->do_model(
565             $c, 'add_related',
566             $c->stash->{object},
567             $c->stash->{rel_name},
568             $c->stash->{foreign_pk_value}
569 3         264 );
570 3         103 $c->res->status(204); # enacted, no content
571 6     6   6402 }
  6         12  
  6         39  
572              
573             =head2 fetch_related
574              
575             Attribute: chained to fetch() like related() is.
576              
577             =cut
578              
579             sub fetch_related : PathPart('') Chained('fetch') CaptureArgs(1) {
580 0     0 1 0 my ( $self, $c, $rel ) = @_;
581 0 0       0 return if $self->has_errors($c);
582 0         0 $c->stash( rel_name => $rel );
583 6     6   6274 }
  6         14  
  6         32  
584              
585             =head2 list_related
586              
587             Attribute: chained to fetch_related().
588              
589             Returns list of related objects.
590              
591             Example:
592              
593             http://yoururl/user/123/group/list
594              
595             will return groups related to user C<123>.
596              
597             =cut
598              
599             sub list_related : PathPart('list') Chained('fetch_related') Args(0) {
600 0     0 1 0 my ( $self, $c, $rel ) = @_;
601 0 0       0 unless ( $self->can_read($c) ) {
602 0         0 $self->throw_error('Permission denied');
603 0         0 return;
604             }
605 0 0       0 return if $self->has_errors($c);
606 0         0 $self->view($c); # set form
607             my $results = $self->do_model(
608             $c, 'iterator_related',
609             $c->stash->{object},
610             $c->stash->{rel_name},
611 0         0 );
612 0         0 $c->stash( results => $results );
613 6     6   6687 }
  6         14  
  6         30  
614              
615             =head2 view_related
616              
617             Attribute: chained to related().
618              
619             Returns list of related objects based on foreign key value.
620              
621             Example:
622              
623             http://yoururl/user/123/group/456/view
624              
625             will return groups of pk C<456> related to user C<123>.
626              
627             =cut
628              
629             sub view_related : PathPart('view') Chained('related') Args(0) {
630 0     0 1 0 my ( $self, $c ) = @_;
631 0 0       0 unless ( $self->can_read($c) ) {
632 0         0 $self->throw_error('Permission denied');
633 0         0 return;
634             }
635 0 0       0 return if $self->has_errors($c);
636 0         0 $self->view($c); # set form
637             my $result = $self->do_model(
638             $c, 'find_related',
639             $c->stash->{object},
640             $c->stash->{rel_name},
641             $c->stash->{foreign_pk_value}
642 0         0 );
643 0         0 $c->stash( results => $result );
644 6     6   6606 }
  6         15  
  6         38  
645              
646             =head1 INTERNAL METHODS
647              
648             The following methods are not visible via the URI namespace but
649             directly affect the dispatch chain.
650              
651             =head2 new( I<c>, I<args> )
652              
653             Sets up the controller instance, detecting and instantiating the model_adapter
654             if set in config().
655              
656             =cut
657              
658             sub new {
659 26     26 1 29730 my ( $class, $app_class, $args ) = @_;
660 26         100 my $self = $class->next::method( $app_class, $args );
661 26         136514 $self->instantiate_model_adapter($app_class);
662 26         11123 return $self;
663             }
664              
665             =head2 form
666              
667             Returns an instance of config->{form_class}. A single form object is instantiated and
668             cached in the controller object. If the form object has a C<clear> or C<reset>
669             method it will be called before returning.
670              
671             =cut
672              
673             sub form {
674 0     0 1 0 my ( $self, $c ) = @_;
675 0   0     0 $self->{_form} ||= $self->form_class->new;
676 0 0       0 if ( $self->{_form}->can('clear') ) {
    0          
677 0         0 $self->{_form}->clear;
678             }
679             elsif ( $self->{_form}->can('reset') ) {
680 0         0 $self->{_form}->reset;
681             }
682 0         0 $self->maybe::next::method($c);
683 0         0 return $self->{_form};
684             }
685              
686             =head2 field_names
687              
688             Returns an array ref of the field names in form(). By default just calls the field_names()
689             method on the form(). Your subclass should implement this method if your form class does
690             not have a field_names() method.
691              
692             =cut
693              
694             sub field_names {
695 0     0 1 0 my ($self) = @_;
696 0         0 return $self->form->field_names;
697             }
698              
699             =head2 can_read( I<context> )
700              
701             Returns true if the current request is authorized to read() the C<object> in
702             stash().
703              
704             Default is true.
705              
706             =cut
707              
708 24     24 1 73 sub can_read {1}
709              
710             =head2 can_write( I<context> )
711              
712             Returns true if the current request is authorized to create() or update()
713             the C<object> in stash().
714              
715             =cut
716              
717 22     22 1 65 sub can_write {1}
718              
719             =head2 form_to_object( I<context> )
720              
721             Should return an object ready to be handed to save_obj(). This is the primary
722             method to override in your subclass, since it will handle all the form validation
723             and population of the object.
724              
725             If form_to_object() returns 0, save() will abort at that point in the process,
726             so form_to_object() should set whatever template and other stash() values
727             should be used in the response.
728              
729             Will throw_error() if not overridden.
730              
731             See CatalystX::CRUD::Controller::RHTMLO for an example.
732              
733             =cut
734              
735             sub form_to_object {
736 0     0 1 0 shift->throw_error("must override form_to_object()");
737             }
738              
739             =head2 save_obj( I<context>, I<object> )
740              
741             Calls the update() or create() method on the I<object> (or model_adapter()),
742             picking the method based on whether C<object_id> in stash()
743             evaluates true (update) or false (create).
744              
745             =cut
746              
747             sub save_obj {
748 9     9 1 21 my ( $self, $c, $obj ) = @_;
749 9 50       29 my $method = $c->stash->{object_id} ? 'update' : 'create';
750 9 100       680 if ( $self->model_adapter ) {
751 3         365 $self->model_adapter->$method( $c, $obj );
752             }
753             else {
754 6         756 $obj->$method;
755             }
756             }
757              
758             =head2 precommit( I<context>, I<object> )
759              
760             Called by save(). If precommit() returns a false value, save() is aborted.
761             If precommit() returns a true value, save_obj() gets called.
762              
763             The default return is true.
764              
765             =cut
766              
767 16     16 1 44 sub precommit {1}
768              
769             =head2 postcommit( I<context>, I<object> )
770              
771             Called in save() after save_obj(). The default behaviour is to issue an external
772             redirect resolving to view().
773              
774             =cut
775              
776             sub postcommit {
777 16     16 1 117 my ( $self, $c, $o ) = @_;
778              
779 16 100 66     58 unless ( defined $c->res->location and length $c->res->location ) {
780 10         759 my $id = $self->make_primary_key_string($o);
781              
782 10 100       232 if ( $c->action->name eq 'rm' ) {
783 2         108 $c->response->redirect( $c->uri_for('') );
784             }
785             else {
786 8         461 $c->response->redirect( $c->uri_for( '', $id, 'view' ) );
787             }
788             }
789              
790 16         4769 1;
791             }
792              
793             =head2 uri_for_view_on_single_result( I<context>, I<results> )
794              
795             Returns 0 unless view_on_single_result returns true.
796              
797             Otherwise, calls the primary_key() value on the first object
798             in I<results> and constructs a uri_for() value to the 'view'
799             action in the same class as the current action.
800              
801             =cut
802              
803             sub uri_for_view_on_single_result {
804 1     1 1 29 my ( $self, $c, $results ) = @_;
805 1 50       8 return 0 unless $self->view_on_single_result;
806              
807             # TODO require $results be a CatalystX::CRUD::Results object
808             # so we can call next() instead of assuming array ref.
809 1         141 my $obj = $results->[0];
810              
811 1         9 my $id = $self->make_primary_key_string($obj);
812              
813             # force stringify $id in case it is an object.
814             # Otherwise uri_for() assumes it is an Action object.
815 1         11 return $c->uri_for( "$id", 'view' );
816             }
817              
818             =head2 make_query( I<context>, I<arg> )
819              
820             This is an optional method. If implemented, do_search() will call this method
821             and pass the return value on to the appropriate model methods. If not implemented,
822             the model will be tested for a make_query() method and it will be called instead.
823              
824             Either the controller subclass or the model B<must> implement a make_query() method.
825              
826             =cut
827              
828             =head2 do_search( I<context>, I<arg> )
829              
830             Prepare and execute a search. Called internally by list()
831             and search().
832              
833             Results are saved in stash() under the C<results> key.
834              
835             If B<naked_results> is true, then results are set just as they are
836             returned from search() or list() (directly from the Model).
837              
838             If B<naked_results> is false (default), then results is a
839             CatalystX::CRUD::Results object.
840              
841             =cut
842              
843             sub do_search {
844 11     11 1 112 my ( $self, $c, @arg ) = @_;
845              
846             # stash the form so it can be re-displayed
847             # subclasses must stick-ify it in their own way.
848 11   33     37 $c->stash->{form} ||= $self->form($c);
849              
850             # if we have no input, just return for initial search
851 11 100 66     820 if ( !@arg && !$c->req->param && $c->action->name eq 'search' ) {
      100        
852 1         206 return;
853             }
854              
855             # turn flag on unless explicitly turned off
856             $c->stash->{view_on_single_result} = 1
857 10 50       1187 unless exists $c->stash->{view_on_single_result};
858              
859 10         1294 my $query;
860 10 50       138 if ( $self->can('make_query') ) {
    50          
861 0         0 $query = $self->make_query( $c, @arg );
862             }
863             elsif ( $self->model_can( $c, 'make_query' ) ) {
864 10         2198 $query = $self->do_model( $c, 'make_query', @arg );
865             }
866             else {
867 0         0 $self->throw_error(
868             "neither controller nor model implement a make_query() method");
869             }
870 10   100     46 my $count = $self->do_model( $c, 'count', $query ) || 0;
871 10         14 my $results;
872 10 50       47 unless ( $c->stash->{fetch_no_results} ) {
873 10         876 $results = $self->do_model( $c, 'search', $query );
874             }
875              
876 10 50 33     91 if ( $results
      33        
      0        
877             && $count == 1
878             && $c->stash->{view_on_single_result}
879             && ( my $uri = $self->uri_for_view_on_single_result( $c, $results ) )
880             )
881             {
882 0 0       0 $c->log->debug("redirect for single_result") if $c->debug;
883 0         0 $c->response->redirect($uri);
884             }
885             else {
886              
887 10         21 my $pager;
888 10 50 66     40 if ( $count && $self->model_can( $c, 'make_pager' ) ) {
889 0         0 $pager = $self->do_model( $c, 'make_pager', $count, $results );
890             }
891              
892             $c->stash->{results}
893 10 50       696 = $self->naked_results
894             ? $results
895             : CatalystX::CRUD::Results->new(
896             { count => $count,
897             pager => $pager,
898             results => $results,
899             query => $query,
900             }
901             );
902             }
903              
904             }
905              
906             =head1 CONVENIENCE METHODS
907              
908             The following methods simply return the config() value of the same name.
909              
910             =over
911              
912             =item form_class
913              
914             =item init_form
915              
916             =item init_object
917              
918             =item model_name
919              
920             =item default_template
921              
922             =item primary_key
923              
924             primary_key may be a single column name or an array ref of multiple
925             column names.
926              
927             =item page_size
928              
929             =item allow_GET_writes
930              
931             =item naked_results
932              
933             =back
934              
935             =cut
936              
937             # see http://use.perl.org/~LTjake/journal/31738
938             # PathPrefix will likely end up in an official Catalyst RSN.
939             # This lets us have a sane default fetch() method without having
940             # to write one in each subclass.
941             sub _parse_PathPrefix_attr {
942 16     16   1918807 my ( $self, $c, $name, $value ) = @_;
943 16         77 return PathPart => $self->path_prefix;
944             }
945              
946             1;
947              
948             __END__
949              
950             =head1 AUTHOR
951              
952             Peter Karman, C<< <perl at peknet.com> >>
953              
954             =head1 BUGS
955              
956             Please report any bugs or feature requests to
957             C<bug-catalystx-crud at rt.cpan.org>, or through the web interface at
958             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=CatalystX-CRUD>.
959             I will be notified, and then you'll automatically be notified of progress on
960             your bug as I make changes.
961              
962             =head1 SUPPORT
963              
964             You can find documentation for this module with the perldoc command.
965              
966             perldoc CatalystX::CRUD
967              
968             You can also look for information at:
969              
970             =over 4
971              
972             =item * Mailing List
973              
974             L<https://groups.google.com/forum/#!forum/catalystxcrud>
975              
976             =item * AnnoCPAN: Annotated CPAN documentation
977              
978             L<http://annocpan.org/dist/CatalystX-CRUD>
979              
980             =item * CPAN Ratings
981              
982             L<http://cpanratings.perl.org/d/CatalystX-CRUD>
983              
984             =item * RT: CPAN's request tracker
985              
986             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=CatalystX-CRUD>
987              
988             =item * Search CPAN
989              
990             L<http://search.cpan.org/dist/CatalystX-CRUD>
991              
992             =back
993              
994             =head1 ACKNOWLEDGEMENTS
995              
996             This module based on Catalyst::Controller::Rose::CRUD by the same author.
997              
998             =head1 COPYRIGHT & LICENSE
999              
1000             Copyright 2007 Peter Karman, all rights reserved.
1001              
1002             This program is free software; you can redistribute it and/or modify it
1003             under the same terms as Perl itself.
1004              
1005             =cut