don't overwrite already set response data when return_object is enabled
[catagits/Catalyst-Controller-DBIC-API.git] / lib / Catalyst / Controller / DBIC / API.pm
1 package Catalyst::Controller::DBIC::API;
2
3 #ABSTRACT: Provides a DBIx::Class web service automagically
4 use Moose;
5 BEGIN { extends 'Catalyst::Controller'; }
6
7 use CGI::Expand ();
8 use DBIx::Class::ResultClass::HashRefInflator;
9 use JSON ();
10 use Test::Deep::NoTest('eq_deeply');
11 use MooseX::Types::Moose(':all');
12 use Moose::Util;
13 use Scalar::Util( 'blessed', 'reftype' );
14 use Try::Tiny;
15 use Catalyst::Controller::DBIC::API::Request;
16 use namespace::autoclean;
17
18 has '_json' => (
19     is         => 'ro',
20     isa        => 'JSON',
21     lazy_build => 1,
22 );
23
24 sub _build__json {
25
26     # no ->utf8 here because the request params get decoded by Catalyst
27     return JSON->new;
28 }
29
30 with 'Catalyst::Controller::DBIC::API::StoredResultSource',
31     'Catalyst::Controller::DBIC::API::StaticArguments';
32
33 with 'Catalyst::Controller::DBIC::API::RequestArguments' => { static => 1 };
34
35 __PACKAGE__->config();
36
37 =head1 SYNOPSIS
38
39   package MyApp::Controller::API::RPC::Artist;
40   use Moose;
41   BEGIN { extends 'Catalyst::Controller::DBIC::API::RPC' }
42
43   __PACKAGE__->config
44     ( # define parent chain action and PathPart
45       action => {
46           setup => {
47               Chained  => '/api/rpc/rpc_base',
48               PathPart => 'artist',
49           }
50       },
51       class            => 'MyAppDB::Artist',
52       resultset_class  => 'MyAppDB::ResultSet::Artist',
53       create_requires  => ['name', 'age'],
54       create_allows    => ['nickname'],
55       update_allows    => ['name', 'age', 'nickname'],
56       update_allows    => ['name', 'age', 'nickname'],
57       select           => ['name', 'age'],
58       prefetch         => ['cds'],
59       prefetch_allows  => [
60           'cds',
61           { cds => 'tracks' },
62           { cds => ['tracks'] },
63       ],
64       ordered_by       => ['age'],
65       search_exposes   => ['age', 'nickname', { cds => ['title', 'year'] }],
66       data_root        => 'list',
67       item_root        => 'data',
68       use_json_boolean => 1,
69       return_object    => 1,
70       );
71
72   # Provides the following functional endpoints:
73   # /api/rpc/artist/create
74   # /api/rpc/artist/list
75   # /api/rpc/artist/id/[id]/delete
76   # /api/rpc/artist/id/[id]/update
77 =cut
78
79 =method_private begin
80
81  :Private
82
83 begin is provided in the base class to setup the Catalyst request object by
84 applying the DBIC::API::Request role.
85
86 =cut
87
88 sub begin : Private {
89     my ( $self, $c ) = @_;
90
91     Moose::Util::ensure_all_roles( $c->req,
92         'Catalyst::Controller::DBIC::API::Request' );
93 }
94
95 =method_protected setup
96
97  :Chained('specify.in.subclass.config') :CaptureArgs(0) :PathPart('specify.in.subclass.config')
98
99 This action is the chain root of the controller. It must either be overridden or
100 configured to provide a base PathPart to the action and also a parent action.
101 For example, for class MyAppDB::Track you might have
102
103   package MyApp::Controller::API::RPC::Track;
104   use Moose;
105   BEGIN { extends 'Catalyst::Controller::DBIC::API::RPC'; }
106
107   __PACKAGE__->config
108     ( action => { setup => { PathPart => 'track', Chained => '/api/rpc/rpc_base' } },
109         ...
110   );
111
112   # or
113
114   sub setup :Chained('/api/rpc/rpc_base') :CaptureArgs(0) :PathPart('track') {
115     my ($self, $c) = @_;
116
117     $self->next::method($c);
118   }
119
120 This action does nothing by default.
121
122 =cut
123
124 sub setup : Chained('specify.in.subclass.config') : CaptureArgs(0) :
125     PathPart('specify.in.subclass.config') { }
126
127 =method_protected deserialize
128
129  :Chained('setup') :CaptureArgs(0) :PathPart('') :ActionClass('Deserialize')
130
131 Absorbs the request data and transforms it into useful bits by using
132 CGI::Expand->expand_hash and a smattering of JSON->decode for a handful of
133 arguments.
134
135 Current only the following arguments are capable of being expressed as JSON:
136
137     search_arg
138     count_arg
139     page_arg
140     ordered_by_arg
141     grouped_by_arg
142     prefetch_arg
143
144 It should be noted that arguments can used mixed modes in with some caveats.
145 Each top level arg can be expressed as CGI::Expand with their immediate child
146 keys expressed as JSON when sending the data application/x-www-form-urlencoded.
147 Otherwise, you can send content as raw json and it will be deserialized as is
148 with no CGI::Expand expasion.
149
150 =cut
151
152 sub deserialize : Chained('setup') : CaptureArgs(0) : PathPart('') :
153     ActionClass('Deserialize') {
154     my ( $self, $c ) = @_;
155     my $req_params;
156
157     if ( $c->req->data && scalar( keys %{ $c->req->data } ) ) {
158         $req_params = $c->req->data;
159     }
160     else {
161         $req_params = CGI::Expand->expand_hash( $c->req->params );
162
163         foreach my $param (
164             @{  [   $self->search_arg,     $self->count_arg,
165                     $self->page_arg,       $self->offset_arg,
166                     $self->ordered_by_arg, $self->grouped_by_arg,
167                     $self->prefetch_arg
168                 ]
169             }
170             )
171         {
172             # these params can also be composed of JSON
173             # but skip if the parameter is not provided
174             next if not exists $req_params->{$param};
175
176             # find out if CGI::Expand was involved
177             if ( ref $req_params->{$param} eq 'HASH' ) {
178                 for my $key ( keys %{ $req_params->{$param} } ) {
179
180                     # copy the value because JSON::XS will alter it
181                     # even if decoding failed
182                     my $value = $req_params->{$param}->{$key};
183                     try {
184                         my $deserialized = $self->_json->decode($value);
185                         $req_params->{$param}->{$key} = $deserialized;
186                     }
187                     catch {
188                         $c->log->debug(
189                             "Param '$param.$key' did not deserialize appropriately: $_"
190                         ) if $c->debug;
191                     }
192                 }
193             }
194             else {
195                 try {
196                     my $value        = $req_params->{$param};
197                     my $deserialized = $self->_json->decode($value);
198                     $req_params->{$param} = $deserialized;
199                 }
200                 catch {
201                     $c->log->debug(
202                         "Param '$param' did not deserialize appropriately: $_"
203                     ) if $c->debug;
204                 }
205             }
206         }
207     }
208
209     $self->inflate_request( $c, $req_params );
210 }
211
212 =method_protected generate_rs
213
214 generate_rs is used by inflate_request to get a resultset for the current
215 request. It receives $c as its only argument.
216 By default it returns a resultset of the controller's class.
217 Override this method if you need to manipulate the default implementation of
218 getting a resultset.
219
220 =cut
221
222 sub generate_rs {
223     my ( $self, $c ) = @_;
224
225     return $c->model( $c->stash->{class} || $self->class );
226 }
227
228 =method_protected inflate_request
229
230 inflate_request is called at the end of deserialize to populate key portions of
231 the request with the useful bits.
232
233 =cut
234
235 sub inflate_request {
236     my ( $self, $c, $params ) = @_;
237
238     try {
239         # set static arguments
240         $c->req->_set_controller($self);
241
242         # set request arguments
243         $c->req->_set_request_data($params);
244
245         # set the current resultset
246         $c->req->_set_current_result_set( $self->generate_rs($c) );
247
248     }
249     catch {
250         $c->log->error($_);
251         $self->push_error( $c, { message => $_ } );
252         $c->detach();
253     }
254 }
255
256 =method_protected object_with_id
257
258  :Chained('deserialize') :CaptureArgs(1) :PathPart('')
259
260 This action is the chain root for all object level actions (such as delete and
261 update) that operate on a single identifer. The provided identifier will be used
262 to find that particular object and add it to the request's store ofobjects.
263
264 Please see L<Catalyst::Controller::DBIC::API::Request::Context> for more
265 details on the stored objects.
266
267 =cut
268
269 sub object_with_id : Chained('deserialize') : CaptureArgs(1) : PathPart('') {
270     my ( $self, $c, $id ) = @_;
271
272     my $vals = $c->req->request_data->{ $self->data_root };
273     unless ( defined($vals) ) {
274
275         # no data root, assume the request_data itself is the payload
276         $vals = $c->req->request_data;
277     }
278
279     try {
280         # there can be only one set of data
281         $c->req->add_object( [ $self->object_lookup( $c, $id ), $vals ] );
282     }
283     catch {
284         $c->log->error($_);
285         $self->push_error( $c, { message => $_ } );
286         $c->detach();
287     }
288 }
289
290 =method_protected objects_no_id
291
292  :Chained('deserialize') :CaptureArgs(0) :PathPart('')
293
294 This action is the chain root for object level actions (such as create, update,
295 or delete) that can involve more than one object. The data stored at the
296 data_root of the request_data will be interpreted as an array of hashes on which
297 to operate. If the hashes are missing an 'id' key, they will be considered a
298 new object to be created. Otherwise, the values in the hash will be used to
299 perform an update. As a special case, a single hash sent will be coerced into
300 an array.
301
302 Please see L<Catalyst::Controller::DBIC::API::Request::Context> for more
303 details on the stored objects.
304
305 =cut
306
307 sub objects_no_id : Chained('deserialize') : CaptureArgs(0) : PathPart('') {
308     my ( $self, $c ) = @_;
309
310     if ( $c->req->has_request_data ) {
311         my $data = $c->req->request_data;
312         my $vals;
313
314         if ( exists( $data->{ $self->data_root } )
315             && defined( $data->{ $self->data_root } ) )
316         {
317             my $root = $data->{ $self->data_root };
318             if ( reftype($root) eq 'ARRAY' ) {
319                 $vals = $root;
320             }
321             elsif ( reftype($root) eq 'HASH' ) {
322                 $vals = [$root];
323             }
324             else {
325                 $c->log->error('Invalid request data');
326                 $self->push_error( $c,
327                     { message => 'Invalid request data' } );
328                 $c->detach();
329             }
330         }
331         else {
332             # no data root, assume the request_data itself is the payload
333             $vals = [ $c->req->request_data ];
334         }
335
336         foreach my $val (@$vals) {
337             unless ( exists( $val->{id} ) ) {
338                 $c->req->add_object(
339                     [ $c->req->current_result_set->new_result( {} ), $val ] );
340                 next;
341             }
342
343             try {
344                 $c->req->add_object(
345                     [ $self->object_lookup( $c, $val->{id} ), $val ] );
346             }
347             catch {
348                 $c->log->error($_);
349                 $self->push_error( $c, { message => $_ } );
350                 $c->detach();
351             }
352         }
353     }
354 }
355
356 =method_protected object_lookup
357
358 This method provides the look up functionality for an object based on 'id'.
359 It is passed the current $c and the id to be used to perform the lookup.
360 Dies if there is no provided id or if no object was found.
361
362 =cut
363
364 sub object_lookup {
365     my ( $self, $c, $id ) = @_;
366
367     die 'No valid ID provided for look up' unless defined $id and length $id;
368     my $object = $c->req->current_result_set->find($id);
369     die "No object found for id '$id'" unless defined $object;
370     return $object;
371 }
372
373 =method_protected list
374
375 list's steps are broken up into three distinct methods:
376
377 =over
378
379 =item L</list_munge_parameters>
380
381 =item L</list_perform_search>
382
383 =item L</list_format_output>.
384
385 =back
386
387 The goal of this method is to call ->search() on the current_result_set,
388 change the resultset class of the result (if needed), and return it in
389 $c->stash->{$self->stash_key}->{$self->data_root}.
390
391 Please see the individual methods for more details on what actual processing
392 takes place.
393
394 If the L</select> config param is defined then the hashes will contain only
395 those columns, otherwise all columns in the object will be returned.
396 L</select> of course supports the function/procedure calling semantics that
397 L<DBIx::Class::ResultSet/select> supports.
398
399 In order to have proper column names in the result, provide arguments in L</as>
400 (which also follows L<DBIx::Class::ResultSet/as> semantics.
401 Similarly L</count>, L</page>, L</grouped_by> and L</ordered_by> affect the
402 maximum number of rows returned as well as the ordering and grouping.
403
404 Note that if select, count, ordered_by or grouped_by request parameters are
405 present, these will override the values set on the class with select becoming
406 bound by the select_exposes attribute.
407
408 If not all objects in the resultset are required then it's possible to pass
409 conditions to the method as request parameters. You can use a JSON string as
410 the 'search' parameter for maximum flexibility or use L<CGI::Expand> syntax.
411 In the second case the request parameters are expanded into a structure and
412 then used as the search condition.
413
414 For example, these request parameters:
415
416  ?search.name=fred&search.cd.artist=luke
417  OR
418  ?search={"name":"fred","cd": {"artist":"luke"}}
419
420 Would result in this search (where 'name' is a column of the result class, 'cd'
421 is a relation of the result class and 'artist' is a column of the related class):
422
423  $rs->search({ name => 'fred', 'cd.artist' => 'luke' }, { join => ['cd'] })
424
425 It is also possible to use a JSON string for expandeded parameters:
426
427  ?search.datetime={"-between":["2010-01-06 19:28:00","2010-01-07 19:28:00"]}
428
429 Note that if pagination is needed, this can be achieved using a combination of
430 the L</count> and L</page> parameters. For example:
431
432   ?page=2&count=20
433
434 Would result in this search:
435
436  $rs->search({}, { page => 2, rows => 20 })
437
438 =cut
439
440 sub list {
441     my ( $self, $c ) = @_;
442
443     $self->list_munge_parameters($c);
444     $self->list_perform_search($c);
445     $self->list_format_output($c);
446
447     # make sure there are no objects lingering
448     $c->req->clear_objects();
449 }
450
451 =method_protected list_munge_parameters
452
453 list_munge_parameters is a noop by default. All arguments will be passed through
454 without any manipulation. In order to successfully manipulate the parameters
455 before the search is performed, simply access
456 $c->req->search_parameters|search_attributes (ArrayRef and HashRef respectively),
457 which correspond directly to ->search($parameters, $attributes).
458 Parameter keys will be in already-aliased form.
459 To store the munged parameters call $c->req->_set_search_parameters($newparams)
460 and $c->req->_set_search_attributes($newattrs).
461
462 =cut
463
464 sub list_munge_parameters { }    # noop by default
465
466 =method_protected list_perform_search
467
468 list_perform_search executes the actual search. current_result_set is updated to
469 contain the result returned from ->search. If paging was requested,
470 search_total_entries will be set as well.
471
472 =cut
473
474 sub list_perform_search {
475     my ( $self, $c ) = @_;
476
477     try {
478         my $req = $c->req;
479
480         my $rs =
481             $req->current_result_set->search( $req->search_parameters,
482             $req->search_attributes );
483
484         $req->_set_current_result_set($rs);
485
486         $req->_set_search_total_entries(
487             $req->current_result_set->pager->total_entries )
488             if $req->has_search_attributes
489             && ( exists( $req->search_attributes->{page} )
490             && defined( $req->search_attributes->{page} )
491             && length( $req->search_attributes->{page} ) );
492     }
493     catch {
494         $c->log->error($_);
495         $self->push_error( $c,
496             { message => 'a database error has occured.' } );
497         $c->detach();
498     }
499 }
500
501 =method_protected list_format_output
502
503 list_format_output prepares the response for transmission across the wire.
504 A copy of the current_result_set is taken and its result_class is set to
505 L<DBIx::Class::ResultClass::HashRefInflator>. Each row in the resultset is then
506 iterated and passed to L</row_format_output> with the result of that call added
507 to the output.
508
509 =cut
510
511 sub list_format_output {
512     my ( $self, $c ) = @_;
513
514     my $rs = $c->req->current_result_set->search;
515     $rs->result_class( $self->result_class ) if $self->result_class;
516
517     try {
518         my $output    = {};
519         my $formatted = [];
520
521         foreach my $row ( $rs->all ) {
522             push( @$formatted, $self->row_format_output( $c, $row ) );
523         }
524
525         $output->{ $self->data_root } = $formatted;
526
527         if ( $c->req->has_search_total_entries ) {
528             $output->{ $self->total_entries_arg } =
529                 $c->req->search_total_entries + 0;
530         }
531
532         $c->stash->{ $self->stash_key } = $output;
533     }
534     catch {
535         $c->log->error($_);
536         $self->push_error( $c,
537             { message => 'a database error has occured.' } );
538         $c->detach();
539     }
540 }
541
542 =method_protected row_format_output
543
544 row_format_output is called each row of the inflated output generated from the
545 search. It receives two arguments, the catalyst context and the hashref that
546 represents the row. By default, this method is merely a passthrough.
547
548 =cut
549
550 sub row_format_output {
551
552     #my ($self, $c, $row) = @_;
553     my ( $self, undef, $row ) = @_;
554     return $row;    # passthrough by default
555 }
556
557 =method_protected item
558
559 item will return a single object called by identifier in the uri. It will be
560 inflated via each_object_inflate.
561
562 =cut
563
564 sub item {
565     my ( $self, $c ) = @_;
566
567     if ( $c->req->count_objects != 1 ) {
568         $c->log->error($_);
569         $self->push_error( $c,
570             { message => 'No objects on which to operate' } );
571         $c->detach();
572     }
573     else {
574         $c->stash->{ $self->stash_key }->{ $self->item_root } =
575             $self->each_object_inflate( $c, $c->req->get_object(0)->[0] );
576     }
577 }
578
579 =method_protected update_or_create
580
581 update_or_create is responsible for iterating any stored objects and performing
582 updates or creates. Each object is first validated to ensure it meets the
583 criteria specified in the L</create_requires> and L</create_allows> (or
584 L</update_allows>) parameters of the controller config. The objects are then
585 committed within a transaction via L</transact_objects> using a closure around
586 L</save_objects>.
587
588 =cut
589
590 sub update_or_create {
591     my ( $self, $c ) = @_;
592
593     if ( $c->req->has_objects ) {
594         $self->validate_objects($c);
595         $self->transact_objects( $c, sub { $self->save_objects( $c, @_ ) } );
596     }
597     else {
598         $c->log->error($_);
599         $self->push_error( $c,
600             { message => 'No objects on which to operate' } );
601         $c->detach();
602     }
603 }
604
605 =method_protected transact_objects
606
607 transact_objects performs the actual commit to the database via $schema->txn_do.
608 This method accepts two arguments, the context and a coderef to be used within
609 the transaction. All of the stored objects are passed as an arrayref for the
610 only argument to the coderef.
611
612 =cut
613
614 sub transact_objects {
615     my ( $self, $c, $coderef ) = @_;
616
617     try {
618         $self->stored_result_source->schema->txn_do( $coderef,
619             $c->req->objects );
620     }
621     catch {
622         $c->log->error($_);
623         $self->push_error( $c,
624             { message => 'a database error has occured.' } );
625         $c->detach();
626     }
627 }
628
629 =method_protected validate_objects
630
631 This is a shortcut method for performing validation on all of the stored objects
632 in the request. Each object's provided values (for create or update) are updated
633 to the allowed values permitted by the various config parameters.
634
635 =cut
636
637 sub validate_objects {
638     my ( $self, $c ) = @_;
639
640     try {
641         foreach my $obj ( $c->req->all_objects ) {
642             $obj->[1] = $self->validate_object( $c, $obj );
643         }
644     }
645     catch {
646         my $err = $_;
647         $c->log->error($err);
648         $err =~ s/\s+at\s+.+\n$//g;
649         $self->push_error( $c, { message => $err } );
650         $c->detach();
651     }
652 }
653
654 =method_protected validate_object
655
656 validate_object takes the context and the object as an argument. It then filters
657 the passed values in slot two of the tuple through the create|update_allows
658 configured. It then returns those filtered values. Values that are not allowed
659 are silently ignored. If there are no values for a particular key, no valid
660 values at all, or multiple of the same key, this method will die.
661
662 =cut
663
664 sub validate_object {
665     my ( $self, $c, $obj ) = @_;
666     my ( $object, $params ) = @$obj;
667
668     my %values;
669     my %requires_map = map { $_ => 1 } @{
670           ( $object->in_storage )
671         ? []
672         : $c->stash->{create_requires} || $self->create_requires
673     };
674
675     my %allows_map = map { ( ref $_ ) ? %{$_} : ( $_ => 1 ) } (
676         keys %requires_map,
677         @{    ( $object->in_storage )
678             ? ( $c->stash->{update_allows} || $self->update_allows )
679             : ( $c->stash->{create_allows} || $self->create_allows )
680         }
681     );
682
683     foreach my $key ( keys %allows_map ) {
684
685         # check value defined if key required
686         my $allowed_fields = $allows_map{$key};
687
688         if ( ref $allowed_fields ) {
689             my $related_source = $object->result_source->related_source($key);
690             my $related_params = $params->{$key};
691             my %allowed_related_map = map { $_ => 1 } @$allowed_fields;
692             my $allowed_related_cols =
693                   ( $allowed_related_map{'*'} )
694                 ? [ $related_source->columns ]
695                 : $allowed_fields;
696
697             foreach my $related_col ( @{$allowed_related_cols} ) {
698                 if (defined(
699                         my $related_col_value =
700                             $related_params->{$related_col}
701                     )
702                     )
703                 {
704                     $values{$key}{$related_col} = $related_col_value;
705                 }
706             }
707         }
708         else {
709             my $value = $params->{$key};
710
711             if ( $requires_map{$key} ) {
712                 unless ( defined($value) ) {
713
714                     # if not defined look for default
715                     $value = $object->result_source->column_info($key)
716                         ->{default_value};
717                     unless ( defined $value ) {
718                         die "No value supplied for ${key} and no default";
719                     }
720                 }
721             }
722
723             # check for multiple values
724             if ( ref($value) && !( reftype($value) eq reftype(JSON::true) ) )
725             {
726                 require Data::Dumper;
727                 die
728                     "Multiple values for '${key}': ${\Data::Dumper::Dumper($value)}";
729             }
730
731             # check exists so we don't just end up with hash of undefs
732             # check defined to account for default values being used
733             $values{$key} = $value
734                 if exists $params->{$key} || defined $value;
735         }
736     }
737
738     unless ( keys %values || !$object->in_storage ) {
739         die 'No valid keys passed';
740     }
741
742     return \%values;
743 }
744
745 =method_protected delete
746
747 delete operates on the stored objects in the request. It first transacts the
748 objects, deleting them in the database using L</transact_objects> and a closure
749 around L</delete_objects>, and then clears the request store of objects.
750
751 =cut
752
753 sub delete {
754     my ( $self, $c ) = @_;
755
756     if ( $c->req->has_objects ) {
757         $self->transact_objects( $c,
758             sub { $self->delete_objects( $c, @_ ) } );
759         $c->req->clear_objects;
760     }
761     else {
762         $c->log->error($_);
763         $self->push_error( $c,
764             { message => 'No objects on which to operate' } );
765         $c->detach();
766     }
767 }
768
769 =method_protected save_objects
770
771 This method is used by update_or_create to perform the actual database
772 manipulations. It iterates each object calling L</save_object>.
773
774 =cut
775
776 sub save_objects {
777     my ( $self, $c, $objects ) = @_;
778
779     foreach my $obj (@$objects) {
780         $self->save_object( $c, $obj );
781     }
782 }
783
784 =method_protected save_object
785
786 save_object first checks to see if the object is already in storage. If so, it
787 calls L</update_object_from_params> otherwise L</insert_object_from_params>.
788
789 =cut
790
791 sub save_object {
792     my ( $self, $c, $obj ) = @_;
793
794     my ( $object, $params ) = @$obj;
795
796     if ( $object->in_storage ) {
797         $self->update_object_from_params( $c, $object, $params );
798     }
799     else {
800         $self->insert_object_from_params( $c, $object, $params );
801     }
802
803 }
804
805 =method_protected update_object_from_params
806
807 update_object_from_params iterates through the params to see if any of them are
808 pertinent to relations. If so it calls L</update_object_relation> with the
809 object, and the relation parameters. Then it calls ->update on the object.
810
811 =cut
812
813 sub update_object_from_params {
814     my ( $self, $c, $object, $params ) = @_;
815
816     foreach my $key ( keys %$params ) {
817         my $value = $params->{$key};
818         if ( ref($value) && !( reftype($value) eq reftype(JSON::true) ) ) {
819             $self->update_object_relation( $c, $object,
820                 delete $params->{$key}, $key );
821         }
822
823         # accessor = colname
824         elsif ( $object->can($key) ) {
825             $object->$key($value);
826         }
827
828         # accessor != colname
829         else {
830             my $accessor =
831                 $object->result_source->column_info($key)->{accessor};
832             $object->$accessor($value);
833         }
834     }
835
836     $object->update();
837 }
838
839 =method_protected update_object_relation
840
841 update_object_relation finds the relation to the object, then calls ->update
842 with the specified parameters.
843
844 =cut
845
846 sub update_object_relation {
847     my ( $self, $c, $object, $related_params, $relation ) = @_;
848     my $row = $object->find_related( $relation, {}, {} );
849
850     if ($row) {
851         foreach my $key ( keys %$related_params ) {
852             my $value = $related_params->{$key};
853             if ( ref($value) && !( reftype($value) eq reftype(JSON::true) ) )
854             {
855                 $self->update_object_relation( $c, $row,
856                     delete $related_params->{$key}, $key );
857             }
858
859             # accessor = colname
860             elsif ( $row->can($key) ) {
861                 $row->$key($value);
862             }
863
864             # accessor != colname
865             else {
866                 my $accessor =
867                     $row->result_source->column_info($key)->{accessor};
868                 $row->$accessor($value);
869             }
870         }
871         $row->update();
872     }
873     else {
874         $object->create_related( $relation, $related_params );
875     }
876 }
877
878 =method_protected insert_object_from_params
879
880 Sets the columns of the object, then calls ->insert.
881
882 =cut
883
884 sub insert_object_from_params {
885
886     #my ($self, $c, $object, $params) = @_;
887     my ( $self, undef, $object, $params ) = @_;
888
889     my %rels;
890     while ( my ( $key, $value ) = each %{$params} ) {
891         if ( ref($value) && !( reftype($value) eq reftype(JSON::true) ) ) {
892             $rels{$key} = $value;
893         }
894
895         # accessor = colname
896         elsif ( $object->can($key) ) {
897             $object->$key($value);
898         }
899
900         # accessor != colname
901         else {
902             my $accessor =
903                 $object->result_source->column_info($key)->{accessor};
904             $object->$accessor($value);
905         }
906     }
907
908     $object->insert;
909
910     while ( my ( $k, $v ) = each %rels ) {
911         $object->create_related( $k, $v );
912     }
913 }
914
915 =method_protected delete_objects
916
917 Iterates through each object calling L</delete_object>.
918
919 =cut
920
921 sub delete_objects {
922     my ( $self, $c, $objects ) = @_;
923
924     map { $self->delete_object( $c, $_->[0] ) } @$objects;
925 }
926
927 =method_protected delete_object
928
929 Performs the actual ->delete on the object.
930
931 =cut
932
933 sub delete_object {
934
935     #my ($self, $c, $object) = @_;
936     my ( $self, undef, $object ) = @_;
937
938     $object->delete;
939 }
940
941 =method_protected end
942
943 end performs the final manipulation of the response before it is serialized.
944 This includes setting the success of the request both at the HTTP layer and
945 JSON layer. If configured with return_object true, and there are stored objects
946 as the result of create or update, those will be inflated according to the
947 schema and get_inflated_columns
948
949 =cut
950
951 sub end : Private {
952     my ( $self, $c ) = @_;
953
954     # don't change the http status code if already set elsewhere
955     unless ( $c->res->status && $c->res->status != 200 ) {
956         if ( $self->has_errors($c) ) {
957             $c->res->status(400);
958         }
959         else {
960             $c->res->status(200);
961         }
962     }
963
964     if ( $c->res->status == 200 ) {
965         $c->stash->{ $self->stash_key }->{success} =
966             $self->use_json_boolean ? JSON::true : 'true';
967         if ( $self->return_object
968             && $c->req->has_objects
969             && ! exists $c->stash->{ $self->stash_key }->{ $self->data_root } ) {
970             my $returned_objects = [];
971             push( @$returned_objects, $self->each_object_inflate( $c, $_ ) )
972                 for map { $_->[0] } $c->req->all_objects;
973             $c->stash->{ $self->stash_key }->{ $self->data_root } =
974                 scalar(@$returned_objects) > 1
975                 ? $returned_objects
976                 : $returned_objects->[0];
977         }
978     }
979     else {
980         $c->stash->{ $self->stash_key }->{success} =
981             $self->use_json_boolean ? JSON::false : 'false';
982         $c->stash->{ $self->stash_key }->{messages} = $self->get_errors($c)
983             if $self->has_errors($c);
984
985         # don't return data for error responses
986         delete $c->stash->{ $self->stash_key }->{ $self->data_root };
987     }
988
989     $c->forward('serialize');
990 }
991
992 =method_protected each_object_inflate
993
994 each_object_inflate executes during L</end> and allows hooking into the process
995 of inflating the objects to return in the response. Receives, the context, and
996 the object as arguments.
997
998 This only executes if L</return_object> if set and if there are any objects to
999 actually return.
1000
1001 =cut
1002
1003 sub each_object_inflate {
1004
1005     #my ($self, $c, $object) = @_;
1006     my ( $self, undef, $object ) = @_;
1007
1008     return { $object->get_columns };
1009 }
1010
1011 =method_protected serialize
1012
1013 multiple actions forward to serialize which uses Catalyst::Action::Serialize.
1014
1015 =cut
1016
1017 # from Catalyst::Action::Serialize
1018 sub serialize : ActionClass('Serialize') { }
1019
1020 =method_protected push_error
1021
1022 Stores an error message into the stash to be later retrieved by L</end>.
1023 Accepts a Dict[message => Str] parameter that defines the error message.
1024
1025 =cut
1026
1027 sub push_error {
1028     my ( $self, $c, $params ) = @_;
1029     die 'Catalyst app object missing'
1030         unless defined $c;
1031     my $error = 'unknown error';
1032     if ( exists $params->{message} ) {
1033         $error = $params->{message};
1034
1035         # remove newline from die "error message\n" which is required to not
1036         # have the filename and line number in the error text
1037         $error =~ s/\n$//;
1038     }
1039     push( @{ $c->stash->{_dbic_crud_errors} }, $error );
1040 }
1041
1042 =method_protected get_errors
1043
1044 Returns all of the errors stored in the stash.
1045
1046 =cut
1047
1048 sub get_errors {
1049     my ( $self, $c ) = @_;
1050     die 'Catalyst app object missing'
1051         unless defined $c;
1052     return $c->stash->{_dbic_crud_errors};
1053 }
1054
1055 =method_protected has_errors
1056
1057 Returns true if errors are stored in the stash.
1058
1059 =cut
1060
1061 sub has_errors {
1062     my ( $self, $c ) = @_;
1063     die 'Catalyst app object missing'
1064         unless defined $c;
1065     return exists $c->stash->{_dbic_crud_errors};
1066 }
1067
1068 =head1 DESCRIPTION
1069
1070 Easily provide common API endpoints based on your L<DBIx::Class> schema classes.
1071 Module provides both RPC and REST interfaces to base functionality.
1072 Uses L<Catalyst::Action::Serialize> and L<Catalyst::Action::Deserialize> to
1073 serialize response and/or deserialise request.
1074
1075 =head1 OVERVIEW
1076
1077 This document describes base functionlity such as list, create, delete, update
1078 and the setting of config attributes. L<Catalyst::Controller::DBIC::API::RPC>
1079 and L<Catalyst::Controller::DBIC::API::REST> describe details of provided
1080 endpoints to those base methods.
1081
1082 You will need to create a controller for each schema class you require API
1083 endpoints for. For example if your schema has Artist and Track, and you want to
1084 provide a RESTful interface to these, you should create
1085 MyApp::Controller::API::REST::Artist and MyApp::Controller::API::REST::Track
1086 which both subclass L<Catalyst::Controller::DBIC::API::REST>.
1087 Similarly if you wanted to provide an RPC style interface then subclass
1088 L<Catalyst::Controller::DBIC::API::RPC>. You then configure these individually
1089 as specified in L</CONFIGURATION>.
1090
1091 Also note that the test suite of this module has an example application used to
1092 run tests against. It maybe helpful to look at that until a better tutorial is
1093 written.
1094
1095 =head2 CONFIGURATION
1096
1097 Each of your controller classes needs to be configured to point at the relevant
1098 schema class, specify what can be updated and so on, as shown in the L</SYNOPSIS>.
1099
1100 The class, create_requires, create_allows and update_requires parameters can
1101 also be set in the stash like so:
1102
1103   sub setup :Chained('/api/rpc/rpc_base') :CaptureArgs(1) :PathPart('any') {
1104     my ($self, $c, $object_type) = @_;
1105
1106     if ($object_type eq 'artist') {
1107       $c->stash->{class} = 'MyAppDB::Artist';
1108       $c->stash->{create_requires} = [qw/name/];
1109       $c->stash->{update_allows} = [qw/name/];
1110     } else {
1111       $self->push_error($c, { message => "invalid object_type" });
1112       return;
1113     }
1114
1115     $self->next::method($c);
1116   }
1117
1118 Generally it's better to have one controller for each DBIC source with the
1119 config hardcoded, but in some cases this isn't possible.
1120
1121 Note that the Chained, CaptureArgs and PathPart are just standard Catalyst
1122 configuration parameters and that then endpoint specified in Chained - in this
1123 case '/api/rpc/rpc_base' - must actually exist elsewhere in your application.
1124 See L<Catalyst::DispatchType::Chained> for more details.
1125
1126 Below are explanations for various configuration parameters. Please see
1127 L<Catalyst::Controller::DBIC::API::StaticArguments> for more details.
1128
1129 =head3 class
1130
1131 Whatever you would pass to $c->model to get a resultset for this class.
1132 MyAppDB::Track for example.
1133
1134 =head3 resultset_class
1135
1136 Desired resultset class after accessing your model. MyAppDB::ResultSet::Track
1137 for example. By default, it's DBIx::Class::ResultClass::HashRefInflator.
1138 Set to empty string to leave resultset class without change.
1139
1140 =head3 stash_key
1141
1142 Controls where in stash request_data should be stored, and defaults to 'response'.
1143
1144 =head3 data_root
1145
1146 By default, the response data of multiple item actions is serialized into
1147 $c->stash->{$self->stash_key}->{$self->data_root} and data_root defaults to
1148 'list' to preserve backwards compatibility. This is now configuable to meet
1149 the needs of the consuming client.
1150
1151 =head3 item_root
1152
1153 By default, the response data of single item actions is serialized into
1154 $c->stash->{$self->stash_key}->{$self->item_root} and item_root default to
1155 'data'.
1156
1157 =head3 use_json_boolean
1158
1159 By default, the response success status is set to a string value of "true" or
1160 "false". If this attribute is true, JSON's true() and false() will be used
1161 instead. Note, this does not effect other internal processing of boolean values.
1162
1163 =head3 count_arg, page_arg, select_arg, search_arg, grouped_by_arg, ordered_by_arg, prefetch_arg, as_arg, total_entries_arg
1164
1165 These attributes allow customization of the component to understand requests
1166 made by clients where these argument names are not flexible and cannot conform
1167 to this components defaults.
1168
1169 =head3 create_requires
1170
1171 Arrayref listing columns required to be passed to create in order for the
1172 request to be valid.
1173
1174 =head3 create_allows
1175
1176 Arrayref listing columns additional to those specified in create_requires that
1177 are not required to create but which create does allow. Columns passed to create
1178 that are not listed in create_allows or create_requires will be ignored.
1179
1180 =head3 update_allows
1181
1182 Arrayref listing columns that update will allow. Columns passed to update that
1183 are not listed here will be ignored.
1184
1185 =head3 select
1186
1187 Arguments to pass to L<DBIx::Class::ResultSet/select> when performing search for
1188 L</list>.
1189
1190 =head3 as
1191
1192 Complements arguments passed to L<DBIx::Class::ResultSet/select> when performing
1193 a search. This allows you to specify column names in the result for RDBMS
1194 functions, etc.
1195
1196 =head3 select_exposes
1197
1198 Columns and related columns that are okay to return in the resultset since
1199 clients can request more or less information specified than the above select
1200 argument.
1201
1202 =head3 prefetch
1203
1204 Arguments to pass to L<DBIx::Class::ResultSet/prefetch> when performing search
1205 for L</list>.
1206
1207 =head3 prefetch_allows
1208
1209 Arrayref listing relationships that are allowed to be prefetched.
1210 This is necessary to avoid denial of service attacks in form of
1211 queries which would return a large number of data
1212 and unwanted disclosure of data.
1213
1214 =head3 grouped_by
1215
1216 Arguments to pass to L<DBIx::Class::ResultSet/group_by> when performing search
1217 for L</list>.
1218
1219 =head3 ordered_by
1220
1221 Arguments to pass to L<DBIx::Class::ResultSet/order_by> when performing search
1222 for L</list>.
1223
1224 =head3 search_exposes
1225
1226 Columns and related columns that are okay to search on. For example if only the
1227 position column and all cd columns were to be allowed
1228
1229  search_exposes => [qw/position/, { cd => ['*'] }]
1230
1231 You can also use this to allow custom columns should you wish to allow them
1232 through in order to be caught by a custom resultset. For example:
1233
1234   package RestTest::Controller::API::RPC::TrackExposed;
1235
1236   ...
1237
1238   __PACKAGE__->config
1239     ( ...,
1240       search_exposes => [qw/position title custom_column/],
1241     );
1242
1243 and then in your custom resultset:
1244
1245   package RestTest::Schema::ResultSet::Track;
1246
1247   use base 'RestTest::Schema::ResultSet';
1248
1249   sub search {
1250     my $self = shift;
1251     my ($clause, $params) = @_;
1252
1253     # test custom attrs
1254     if (my $pretend = delete $clause->{custom_column}) {
1255       $clause->{'cd.year'} = $pretend;
1256     }
1257     my $rs = $self->SUPER::search(@_);
1258   }
1259
1260 =head3 count
1261
1262 Arguments to pass to L<DBIx::Class::ResultSet/rows> when performing search for
1263 L</list>.
1264
1265 =head3 page
1266
1267 Arguments to pass to L<DBIx::Class::ResultSet/page> when performing search for
1268 L</list>.
1269
1270 =head1 EXTENDING
1271
1272 By default the create, delete and update actions will not return anything apart
1273 from the success parameter set in L</end>, often this is not ideal but the
1274 required behaviour varies from application to application. So normally it's
1275 sensible to write an intermediate class which your main controller classes
1276 subclass from.
1277
1278 For example if you wanted create to return the JSON for the newly created
1279 object you might have something like:
1280
1281   package MyApp::ControllerBase::DBIC::API::RPC;
1282   ...
1283   use Moose;
1284   BEGIN { extends 'Catalyst::Controller::DBIC::API::RPC' };
1285   ...
1286   sub create :Chained('setup') :Args(0) :PathPart('create') {
1287     my ($self, $c) = @_;
1288
1289     # $c->req->all_objects will contain all of the created
1290     $self->next::method($c);
1291
1292     if ($c->req->has_objects) {
1293       # $c->stash->{$self->stash_key} will be serialized in the end action
1294       $c->stash->{$self->stash_key}->{$self->data_root} = [ map { { $_->get_inflated_columns } } ($c->req->all_objects) ] ;
1295     }
1296   }
1297
1298   package MyApp::Controller::API::RPC::Track;
1299   ...
1300   use Moose;
1301   BEGIN { extends 'MyApp::ControllerBase::DBIC::API::RPC' };
1302   ...
1303
1304 It should be noted that the return_object attribute will produce the above
1305 result for you, free of charge.
1306
1307 Similarly you might want create, update and delete to all forward to the list
1308 action once they are done so you can refresh your view. This should also be
1309 simple enough.
1310
1311 If more extensive customization is required, it is recommened to peer into the
1312 roles that comprise the system and make use
1313
1314 =head1 NOTES
1315
1316 It should be noted that version 1.004 and above makes a rapid depature from the
1317 status quo. The internals were revamped to use more modern tools such as Moose
1318 and its role system to refactor functionality out into self-contained roles.
1319
1320 To this end, internally, this module now understands JSON boolean values (as
1321 represented by the JSON module) and will Do The Right Thing in handling those
1322 values. This means you can have ColumnInflators installed that can covert
1323 between JSON booleans and whatever your database wants for boolean values.
1324
1325 Validation for various *_allows or *_exposes is now accomplished via
1326 Data::DPath::Validator with a lightly simplified, via a subclass of
1327 Data::DPath::Validator::Visitor.
1328
1329 The rough jist of the process goes as follows: Arguments provided to those
1330 attributes are fed into the Validator and Data::DPaths are generated.
1331 Then incoming requests are validated against these paths generated.
1332 The validator is set in "loose" mode meaning only one path is required to match.
1333 For more information, please see L<Data::DPath::Validator> and more specifically
1334 L<Catalyst::Controller::DBIC::API::Validator>.
1335
1336 Since 2.001:
1337 Transactions are used. The stash is put aside in favor of roles applied to the
1338 request object with additional accessors.
1339 Error handling is now much more consistent with most errors immediately detaching.
1340 The internals are much easier to read and understand with lots more documentation.
1341
1342 Since 2.006:
1343 The SQL::Abstract -and, -not and -or operators are supported.
1344
1345 =cut
1346
1347 1;