Added support for tweaking where to store response in stash
[catagits/Catalyst-Controller-DBIC-API.git] / lib / Catalyst / Controller / DBIC / API.pm
index acc8722..2a72e28 100644 (file)
@@ -15,9 +15,9 @@ use Try::Tiny;
 use Catalyst::Controller::DBIC::API::Request;
 use namespace::autoclean;
 
-with 'Catalyst::Controller::DBIC::API::StoredResultSource';
-with 'Catalyst::Controller::DBIC::API::StaticArguments';
-with 'Catalyst::Controller::DBIC::API::RequestArguments' => { static => 1 };
+with 'Catalyst::Controller::DBIC::API::StoredResultSource',
+     'Catalyst::Controller::DBIC::API::StaticArguments',
+     'Catalyst::Controller::DBIC::API::RequestArguments' => { static => 1 };
 
 __PACKAGE__->config();
 
@@ -29,24 +29,25 @@ __PACKAGE__->config();
 
   __PACKAGE__->config
     ( action => { setup => { PathPart => 'artist', Chained => '/api/rpc/rpc_base' } }, # define parent chain action and partpath
-      class => 'MyAppDB::Artist', # DBIC schema class
-      create_requires => ['name', 'age'], # columns required to create
-      create_allows => ['nickname'], # additional non-required columns that create allows
-      update_allows => ['name', 'age', 'nickname'], # columns that update allows
-      update_allows => ['name', 'age', 'nickname'], # columns that update allows
-      select => [qw/name age/], # columns that data returns
-      prefetch => ['cds'], # relationships that are prefetched when no prefetch param is passed
-      prefetch_allows => [ # every possible prefetch param allowed
+      class            => 'MyAppDB::Artist',
+      result_class     => 'MyAppDB::ResultSet::Artist',
+      create_requires  => ['name', 'age'],
+      create_allows    => ['nickname'],
+      update_allows    => ['name', 'age', 'nickname'],
+      update_allows    => ['name', 'age', 'nickname'],
+      select           => [qw/name age/],
+      prefetch         => ['cds'],
+      prefetch_allows  => [
           'cds',
           qw/ cds /,
           { cds => 'tracks' },
-          { cds => [qw/ tracks /] }
+          { cds => [qw/ tracks /] },
       ],
-      ordered_by => [qw/age/], # order of generated list
-      search_exposes => [qw/age nickname/, { cds => [qw/title year/] }], # columns that can be searched on via list
-      data_root => 'data' # defaults to "list" for backwards compatibility
-      use_json_boolean => 1, # use JSON::Any::true|false in the response instead of strings
-      return_object => 1, # makes create and update actions return the object
+      ordered_by       => [qw/age/],
+      search_exposes   => [qw/age nickname/, { cds => [qw/title year/] }],
+      data_root        => 'data',
+      use_json_boolean => 1,
+      return_object    => 1,
       );
 
   # Provides the following functional endpoints:
@@ -180,7 +181,9 @@ generate_rs is used by inflate_request to generate the resultset stored in the c
 
 sub generate_rs
 {
-    my ($self, $c) = @_;
+    #my ($self, $c) = @_;
+    my ($self) = @_;
+
     return $self->stored_result_source->resultset;
 }
 
@@ -329,7 +332,7 @@ sub object_lookup
 
 list's steps are broken up into three distinct methods: L</list_munge_parameters>, L</list_perform_search>, and L</list_format_output>.
 
-The goal of this method is to call ->search() on the current_result_set, HashRefInflator the result, and return it in $c->stash->{response}->{$self->data_root}. Please see the individual methods for more details on what actual processing takes place.
+The goal of this method is to call ->search() on the current_result_set, change resultset class of the result (if needed), and return it in $c->stash->{$self->stash_key}->{$self->data_root}. Please see the individual methods for more details on what actual processing takes place.
 
 If the L</select> config param is defined then the hashes will contain only those columns, otherwise all columns in the object will be returned. L</select> of course supports the function/procedure calling semantics that L<DBIx::Class::ResultSet/select>. In order to have proper column names in the result, provide arguments in L</as> (which also follows L<DBIx::Class::ResultSet/as> semantics. Similarly L</count>, L</page>, L</grouped_by> and L</ordered_by> affect the maximum number of rows returned as well as the ordering and grouping. Note that if select, count, ordered_by or grouped_by request parameters are present then these will override the values set on the class with select becoming bound by the select_exposes attribute.
 
@@ -424,7 +427,7 @@ sub list_format_output
     my ($self, $c) = @_;
 
     my $rs = $c->req->current_result_set->search;
-    $rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
+    $rs->result_class($self->result_class) if $self->result_class;
 
     try
     {
@@ -443,7 +446,7 @@ sub list_format_output
             $output->{$self->total_entries_arg} = $c->req->search_total_entries + 0;
         }
 
-        $c->stash->{response} = $output;
+        $c->stash->{$self->stash_key} = $output;
     }
     catch
     {
@@ -461,7 +464,8 @@ row_format_output is called each row of the inflated output generated from the s
 
 sub row_format_output
 {
-    my ($self, $c, $row) = @_;
+    #my ($self, $c, $row) = @_;
+    my ($self, undef, $row) = @_;
     return $row; # passthrough by default
 }
 
@@ -483,7 +487,7 @@ sub item
     }
     else
     {
-        $c->stash->{response}->{$self->item_root} = $self->each_object_inflate($c, $c->req->get_object(0)->[0]);
+        $c->stash->{$self->stash_key}->{$self->item_root} = $self->each_object_inflate($c, $c->req->get_object(0)->[0]);
     }
 }
 
@@ -734,9 +738,18 @@ sub update_object_from_params
         {
             $self->update_object_relation($c, $object, delete $params->{$key}, $key);
         }
+        # accessor = colname
+        elsif ($object->can($key)) {
+            $object->$key($value);
+        }
+        # accessor != colname
+        else {
+            my $accessor = $object->result_source->column_info($key)->{accessor};
+            $object->$accessor($value);
+        }
     }
 
-    $object->update($params);
+    $object->update();
 }
 
 =method_protected update_object_relation
@@ -751,7 +764,23 @@ sub update_object_relation
     my $row = $object->find_related($relation, {} , {});
 
     if ($row) {
-        $row->update($related_params);
+        foreach my $key (keys %$related_params) {
+            my $value = $related_params->{$key};
+            if (ref($value) && !(reftype($value) eq reftype(JSON::Any::true)))
+            {
+                $self->update_object_relation($c, $row, delete $related_params->{$key}, $key);
+            }
+            # accessor = colname
+            elsif ($row->can($key)) {
+                $row->$key($value);
+            }
+            # accessor != colname
+            else {
+                my $accessor = $row->result_source->column_info($key)->{accessor};
+                $row->$accessor($value);
+            }
+        }
+        $row->update();
     }
     else {
         $object->create_related($relation, $related_params);
@@ -766,7 +795,8 @@ insert_object_from_params sets the columns for the object, then calls ->insert
 
 sub insert_object_from_params
 {
-    my ($self, $c, $object, $params) = @_;
+    #my ($self, $c, $object, $params) = @_;
+    my ($self, undef, $object, $params) = @_;
 
     my %rels;
     while (my ($k, $v) = each %{ $params }) {
@@ -806,7 +836,8 @@ Performs the actual ->delete on the object
 
 sub delete_object
 {
-    my ($self, $c, $object) = @_;
+    #my ($self, $c, $object) = @_;
+    my ($self, undef, $object) = @_;
 
     $object->delete;
 }
@@ -827,25 +858,25 @@ sub end :Private
     # Check for errors caught elsewhere
     if ( $c->res->status and $c->res->status != 200 ) {
         $default_status = $c->res->status;
-        $c->stash->{response}->{success} = $self->use_json_boolean ? JSON::Any::false : 'false';
+        $c->stash->{$self->stash_key}->{success} = $self->use_json_boolean ? JSON::Any::false : 'false';
     } elsif ($self->get_errors($c)) {
-        $c->stash->{response}->{messages} = $self->get_errors($c);
-        $c->stash->{response}->{success} = $self->use_json_boolean ? JSON::Any::false : 'false';
+        $c->stash->{$self->stash_key}->{messages} = $self->get_errors($c);
+        $c->stash->{$self->stash_key}->{success} = $self->use_json_boolean ? JSON::Any::false : 'false';
         $default_status = 400;
     } else {
-        $c->stash->{response}->{success} = $self->use_json_boolean ? JSON::Any::true : 'true';
+        $c->stash->{$self->stash_key}->{success} = $self->use_json_boolean ? JSON::Any::true : 'true';
         $default_status = 200;
     }
 
     unless ($default_status == 200)
     {
-        delete $c->stash->{response}->{$self->data_root};
+        delete $c->stash->{$self->stash_key}->{$self->data_root};
     }
     elsif($self->return_object && $c->req->has_objects)
     {
         my $returned_objects = [];
         push(@$returned_objects, $self->each_object_inflate($c, $_)) for map { $_->[0] } $c->req->all_objects;
-        $c->stash->{response}->{$self->data_root} = scalar(@$returned_objects) > 1 ? $returned_objects : $returned_objects->[0];
+        $c->stash->{$self->stash_key}->{$self->data_root} = scalar(@$returned_objects) > 1 ? $returned_objects : $returned_objects->[0];
     }
 
     $c->res->status( $default_status || 200 );
@@ -862,11 +893,18 @@ This only executes if L</return_object> if set and if there are any objects to a
 
 sub each_object_inflate
 {
-    my ($self, $c, $object) = @_;
+    #my ($self, $c, $object) = @_;
+    my ($self, undef, $object) = @_;
 
     return { $object->get_columns };
 }
 
+=method_protected serialize
+
+multiple actions forward to serialize which uses Catalyst::Action::Serialize.
+
+=cut
+
 # from Catalyst::Action::Serialize
 sub serialize :ActionClass('Serialize') { }
 
@@ -879,7 +917,14 @@ push_error stores an error message into the stash to be later retrieved by L</en
 sub push_error
 {
     my ( $self, $c, $params ) = @_;
-    push( @{$c->stash->{_dbic_crud_errors}}, $params->{message} || 'unknown error' );
+    my $error = 'unknown error';
+    if (exists $params->{message}) {
+        $error = $params->{message};
+        # remove newline from die "error message\n" which is required to not
+        # have the filename and line number in the error text
+        $error =~ s/\n$//;
+    }
+    push( @{$c->stash->{_dbic_crud_errors}}, $error);
 }
 
 =method_protected get_errors
@@ -937,9 +982,17 @@ Below are explanations for various configuration parameters. Please see L<Cataly
 
 Whatever you would pass to $c->model to get a resultset for this class. MyAppDB::Track for example.
 
+=head3 resultset_class
+
+Desired resultset class after accessing your model. MyAppDB::ResultSet::Track for example. By default, it's DBIx::Class::ResultClass::HashRefInflator. Set to empty string to leave resultset class without change.
+
+=head3 stash_key
+
+Controls where in stash request_data should be stored, and defaults to 'response'.
+
 =head3 data_root
 
-By default, the response data is serialized into $c->stash->{response}->{$self->data_root} and data_root defaults to 'list' to preserve backwards compatibility. This is now configuable to meet the needs of the consuming client.
+By default, the response data is serialized into $c->stash->{$self->stash_key}->{$self->data_root} and data_root defaults to 'list' to preserve backwards compatibility. This is now configuable to meet the needs of the consuming client.
 
 =head3 use_json_boolean
 
@@ -1032,7 +1085,7 @@ Arguments to pass to L<DBIx::Class::ResultSet/rows> when performing search for L
 
 =head3 page
 
-Arguments to pass to L<DBIx::Class::ResultSet/rows> when performing search for L</list>.
+Arguments to pass to L<DBIx::Class::ResultSet/page> when performing search for L</list>.
 
 =head1 EXTENDING
 
@@ -1052,8 +1105,8 @@ For example if you wanted create to return the JSON for the newly created object
     $self->next::method($c);
 
     if ($c->req->has_objects) {
-      # $c->stash->{response} will be serialized in the end action
-      $c->stash->{response}->{$self->data_root} = [ map { { $_->get_inflated_columns } } ($c->req->all_objects) ] ;
+      # $c->stash->{$self->stash_key} will be serialized in the end action
+      $c->stash->{$self->stash_key}->{$self->data_root} = [ map { { $_->get_inflated_columns } } ($c->req->all_objects) ] ;
     }
   }