improved pod for list_munge_parameters
[catagits/Catalyst-Controller-DBIC-API.git] / lib / Catalyst / Controller / DBIC / API.pm
index 89aca62..229237d 100644 (file)
@@ -2,7 +2,7 @@ package Catalyst::Controller::DBIC::API;
 
 #ABSTRACT: Provides a DBIx::Class web service automagically
 use Moose;
-BEGIN { extends 'Catalyst::Controller'; }
+BEGIN { extends 'Catalyst::Controller::ActionRole'; }
 
 use CGI::Expand ();
 use DBIx::Class::ResultClass::HashRefInflator;
@@ -56,22 +56,20 @@ __PACKAGE__->config();
   # /api/rpc/artist/id/[id]/update
 =cut
 
-=method_protected begin
+=method_private begin
 
  :Private
 
-A begin method is provided to apply the L<Catalyst::Controller::DBIC::API::Request> role to $c->request, and perform deserialization and validation of request parameters
+begin is provided in the base class to setup the Catalyst Request object, by applying the DBIC::API::Request role.
 
 =cut
 
 sub begin :Private
 {
-    $DB::single = 1;
     my ($self, $c) = @_;
-    
+
     Catalyst::Controller::DBIC::API::Request->meta->apply($c->req)
         unless Moose::Util::does_role($c->req, 'Catalyst::Controller::DBIC::API::Request');
-    $c->forward('deserialize');
 }
 
 =method_protected setup
@@ -81,10 +79,11 @@ sub begin :Private
 This action is the chain root of the controller. It must either be overridden or configured to provide a base pathpart to the action and also a parent action. For example, for class MyAppDB::Track you might have
 
   package MyApp::Controller::API::RPC::Track;
-  use base qw/Catalyst::Controller::DBIC::API::RPC/;
+  use Moose;
+  BEGIN { extends 'Catalyst::Controller::DBIC::API::RPC'; }
 
   __PACKAGE__->config
-    ( action => { setup => { PathPart => 'track', Chained => '/api/rpc/rpc_base' } }, 
+    ( action => { setup => { PathPart => 'track', Chained => '/api/rpc/rpc_base' } },
        ...
   );
 
@@ -96,104 +95,16 @@ This action is the chain root of the controller. It must either be overridden or
     $self->next::method($c);
   }
 
-This action will populate $c->req->current_result_set with $self->stored_result_source->resultset for other actions in the chain to use.
+This action does nothing by default.
 
 =cut
 
-sub setup :Chained('specify.in.subclass.config') :CaptureArgs(0) :PathPart('specify.in.subclass.config')
-{
-    $DB::single = 1;
-    my ($self, $c) = @_;
-
-    $c->req->_set_current_result_set($self->stored_result_source->resultset);
-}
-
-=method_protected object
-
- :Chained('setup') :CaptureArgs(1) :PathPart('')
-
-This action is the chain root for all object level actions (such as delete and update). If an identifier is passed it will be used to find that particular object and add it to the request's store of objects. Otherwise, the data stored at the data_root of the request_data will be interpreted as an array of objects on which to operate. If the hashes are missing an 'id' key, they will be considered a new object to be created, otherwise, the values in the hash will be used to perform an update. Please see L<Catalyst::Controller::DBIC::API::Context> for more details on the stored objects.
-
-=cut
-
-sub object :Chained('setup') :CaptureArgs(1) :PathPart('')
-{
-       my ($self, $c, $id) = @_;
-
-    my $vals = $c->req->request_data->{$self->data_root};
-    unless(defined($vals))
-    {
-        # no data root, assume the request_data itself is the payload
-        $vals = [$c->req->request_data || {}];
-    }
-    elsif(reftype($vals) eq 'HASH')
-    {
-        $vals = [ $vals ];
-    }
-
-    if(defined($id))
-    {
-        try
-        {
-            # there can be only one set of data
-            $c->req->add_object([$self->object_lookup($c, $id), $vals->[0]]);
-        }
-        catch
-        {
-            $c->log->error($_);
-            $self->push_error($c, { message => $_ });
-            $c->detach();
-        }
-    }
-    else
-    {
-        unless(reftype($vals) eq 'ARRAY')
-        {
-            $c->log->error('Invalid request data');
-            $self->push_error($c, { message => 'Invalid request data' });
-            $c->detach();
-        }
-
-        foreach my $val (@$vals)
-        {
-            unless(exists($val->{id}))
-            {
-                $c->req->add_object([$c->req->current_result_set->new_result({}), $val]);
-                next;
-            }
-
-            try
-            {
-                $c->req->add_object([$self->object_lookup($c, $val->{id}), $val]);
-            }
-            catch
-            {
-                $c->log->error($_);
-                $self->push_error($c, { message => $_ });
-                $c->detach();
-            }
-        }
-    }
-}
-
-=method_protected object_lookup
-
-This method provides the look up functionality for an object based on 'id'. It is passed the current $c and the $id to be used to perform the lookup. Dies if there is no provided $id or if no object was found.
-
-=cut
-
-sub object_lookup
-{
-    my ($self, $c, $id) = @_;
-
-    die 'No valid ID provided for look up' unless defined $id and length $id;
-    my $object = $c->req->current_result_set->find($id);
-    die "No object found for id '$id'" unless defined $object;
-    return $object;
-}
+sub setup :Chained('specify.in.subclass.config') :CaptureArgs(0) :PathPart('specify.in.subclass.config') {}
 
 =method_protected deserialize
 
+ :Chained('setup') :CaptureArgs(0) :PathPart('') :ActionClass('Deserialize')
+
 deserialize absorbs the request data and transforms it into useful bits by using CGI::Expand->expand_hash and a smattering of JSON::Any->from_json for a handful of arguments. Current only the following arguments are capable of being expressed as JSON:
 
     search_arg
@@ -203,13 +114,12 @@ deserialize absorbs the request data and transforms it into useful bits by using
     grouped_by_arg
     prefetch_arg
 
-It should be noted that arguments can used mixed modes in with some caveats. Each top level arg can be expressed as CGI::Expand with their immediate child keys expressed as JSON.
+It should be noted that arguments can used mixed modes in with some caveats. Each top level arg can be expressed as CGI::Expand with their immediate child keys expressed as JSON when sending the data application/x-www-form-urlencoded. Otherwise, you can send content as raw json and it will be deserialized as is with no CGI::Expand expasion.
 
 =cut
 
-sub deserialize :ActionClass('Deserialize')
+sub deserialize :Chained('setup') :CaptureArgs(0) :PathPart('') :ActionClass('Deserialize')
 {
-    $DB::single = 1;
     my ($self, $c) = @_;
     my $req_params;
 
@@ -217,7 +127,7 @@ sub deserialize :ActionClass('Deserialize')
     {
         $req_params = $c->req->data;
     }
-    else 
+    else
     {
         $req_params = CGI::Expand->expand_hash($c->req->params);
 
@@ -237,7 +147,7 @@ sub deserialize :ActionClass('Deserialize')
                         $req_params->{$param}->{$key} = $deserialized;
                     }
                     catch
-                    { 
+                    {
                         $c->log->debug("Param '$param.$key' did not deserialize appropriately: $_")
                         if $c->debug;
                     }
@@ -251,17 +161,29 @@ sub deserialize :ActionClass('Deserialize')
                     $req_params->{$param} = $deserialized;
                 }
                 catch
-                { 
+                {
                     $c->log->debug("Param '$param' did not deserialize appropriately: $_")
                     if $c->debug;
                 }
             }
         }
     }
-    
+
     $self->inflate_request($c, $req_params);
 }
 
+=method_protected generate_rs
+
+generate_rs is used by inflate_request to generate the resultset stored in the current request. It receives $c as its only argument. And by default it merely returns the resultset from the stored_result_source on the controller. Override this method if you need to manipulate the default implementation of getting the resultset from the controller.
+
+=cut
+
+sub generate_rs
+{
+    my ($self, $c) = @_;
+    return $self->stored_result_source->resultset;
+}
+
 =method_protected inflate_request
 
 inflate_request is called at the end of deserialize to populate key portions of the request with the useful bits
@@ -270,17 +192,19 @@ inflate_request is called at the end of deserialize to populate key portions of
 
 sub inflate_request
 {
-    $DB::single = 1;
     my ($self, $c, $params) = @_;
 
     try
     {
         # set static arguments
-        $c->req->_set_controller($self); 
+        $c->req->_set_controller($self);
 
         # set request arguments
         $c->req->_set_request_data($params);
-        
+
+        # set the current resultset
+        $c->req->_set_current_result_set($self->generate_rs($c));
+
     }
     catch
     {
@@ -288,16 +212,124 @@ sub inflate_request
         $self->push_error($c, { message => $_ });
         $c->detach();
     }
-    
 }
 
-=method_protected list
+=method_protected object_with_id
 
- :Private
+ :Chained('deserialize') :CaptureArgs(1) :PathPart('')
+
+This action is the chain root for all object level actions (such as delete and update) that operate on a single identifer. The provided identifier will be used to find that particular object and add it to the request's store of objects. Please see L<Catalyst::Controller::DBIC::API::Context> for more details on the stored objects.
+
+=cut
+
+sub object_with_id :Chained('deserialize') :CaptureArgs(1) :PathPart('')
+{
+       my ($self, $c, $id) = @_;
+
+    my $vals = $c->req->request_data->{$self->data_root};
+    unless(defined($vals))
+    {
+        # no data root, assume the request_data itself is the payload
+        $vals = $c->req->request_data;
+    }
+
+    try
+    {
+        # there can be only one set of data
+        $c->req->add_object([$self->object_lookup($c, $id), $vals]);
+    }
+    catch
+    {
+        $c->log->error($_);
+        $self->push_error($c, { message => $_ });
+        $c->detach();
+    }
+}
+
+=method_protected objects_no_id
+
+ :Chained('deserialize') :CaptureArgs(0) :PathPart('')
+
+This action is the chain root for object level actions (such as create, update, or delete) that can involve more than one object. The data stored at the data_root of the request_data will be interpreted as an array of hashes on which to operate. If the hashes are missing an 'id' key, they will be considered a new object to be created. Otherwise, the values in the hash will be used to perform an update. As a special case, a single hash sent will be coerced into an array. Please see L<Catalyst::Controller::DBIC::API::Context> for more details on the stored objects.
+
+=cut
+
+sub objects_no_id :Chained('deserialize') :CaptureArgs(0) :PathPart('')
+{
+    my ($self, $c) = @_;
+
+    if($c->req->has_request_data)
+    {
+        my $data = $c->req->request_data;
+        my $vals;
+
+        if(exists($data->{$self->data_root}) && defined($data->{$self->data_root}))
+        {
+            my $root = $data->{$self->data_root};
+            if(reftype($root) eq 'ARRAY')
+            {
+                $vals = $root;
+            }
+            elsif(reftype($root) eq 'HASH')
+            {
+                $vals = [$root];
+            }
+            else
+            {
+                $c->log->error('Invalid request data');
+                $self->push_error($c, { message => 'Invalid request data' });
+                $c->detach();
+            }
+        }
+        else
+        {
+            # no data root, assume the request_data itself is the payload
+            $vals = [$c->req->request_data];
+        }
+
+        foreach my $val (@$vals)
+        {
+            unless(exists($val->{id}))
+            {
+                $c->req->add_object([$c->req->current_result_set->new_result({}), $val]);
+                next;
+            }
+
+            try
+            {
+                $c->req->add_object([$self->object_lookup($c, $val->{id}), $val]);
+            }
+            catch
+            {
+                $c->log->error($_);
+                $self->push_error($c, { message => $_ });
+                $c->detach();
+            }
+        }
+    }
+}
 
-List level action chained from L</setup>. List's steps are broken up into three distinct methods: L</list_munge_parameters>, L</list_perform_search>, and L</list_format_output>.
+=method_protected object_lookup
 
-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}. Pleasee see the individual methods for more details on what actual processing takes place.
+This method provides the look up functionality for an object based on 'id'. It is passed the current $c and the $id to be used to perform the lookup. Dies if there is no provided $id or if no object was found.
+
+=cut
+
+sub object_lookup
+{
+    my ($self, $c, $id) = @_;
+
+    die 'No valid ID provided for look up' unless defined $id and length $id;
+    my $object = $c->req->current_result_set->find($id);
+    die "No object found for id '$id'" unless defined $object;
+    return $object;
+}
+
+=method_protected list
+
+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.
 
 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.
 
@@ -322,24 +354,27 @@ Note that if pagination is needed, this can be achieved using a combination of t
   ?page=2&count=20
 
 Would result in this search:
+
  $rs->search({}, { page => 2, rows => 20 })
 
 =cut
 
-sub list :Private 
+sub list
 {
-    $DB::single = 1;
     my ($self, $c) = @_;
 
     $self->list_munge_parameters($c);
     $self->list_perform_search($c);
     $self->list_format_output($c);
+
+    # make sure there are no objects lingering
+    $c->req->clear_objects();
 }
 
 =method_protected list_munge_parameters
 
 list_munge_parameters is a noop by default. All arguments will be passed through without any manipulation. In order to successfully manipulate the parameters before the search is performed, simply access $c->req->search_parameters|search_attributes (ArrayRef and HashRef respectively), which correspond directly to ->search($parameters, $attributes). Parameter keys will be in already-aliased form.
+To store the munged parameters call $c->req->_set_search_parameters($newparams) and $c->req->_set_search_attributes($newattrs).
 
 =cut
 
@@ -353,28 +388,22 @@ list_perform_search executes the actual search. current_result_set is updated to
 
 sub list_perform_search
 {
-    $DB::single = 1;
     my ($self, $c) = @_;
-    
-    try 
+
+    try
     {
         my $req = $c->req;
-        
+
         my $rs = $req->current_result_set->search
         (
-            $req->search_parameters, 
+            $req->search_parameters,
             $req->search_attributes
         );
 
         $req->_set_current_result_set($rs);
 
         $req->_set_search_total_entries($req->current_result_set->pager->total_entries)
-            if $req->has_search_attributes && 
-            (
-                (exists($req->search_attributes->{page}) && defined($req->search_attributes->{page}) && length($req->search_attributes->{page}))
-                ||(exists($req->search_attributes->{offset}) && defined($req->search_attributes->{offset}) && length($req->search_attributes->{offset})) 
-                ||(exists($req->search_attributes->{rows}) && defined($req->search_attributes->{rows}) && length($req->search_attributes->{rows}))
-            );
+            if $req->has_search_attributes && (exists($req->search_attributes->{page}) && defined($req->search_attributes->{page}) && length($req->search_attributes->{page}));
     }
     catch
     {
@@ -392,22 +421,21 @@ list_format_output prepares the response for transmission across the wire. A cop
 
 sub list_format_output
 {
-    $DB::single = 1;
     my ($self, $c) = @_;
 
     my $rs = $c->req->current_result_set->search;
     $rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
-    
+
     try
     {
         my $output = {};
         my $formatted = [];
-        
+
         foreach my $row ($rs->all)
         {
-            push(@$formatted, $self->row_format_output($row));
+            push(@$formatted, $self->row_format_output($c, $row));
         }
-        
+
         $output->{$self->data_root} = $formatted;
 
         if ($c->req->has_search_total_entries)
@@ -427,29 +455,52 @@ sub list_format_output
 
 =method_protected row_format_output
 
-row_format_output is called each row of the inflated output generated from the search. It receives only one argument, the hashref that represents the row. By default, this method is merely a passthrough.
+row_format_output is called each row of the inflated output generated from the search. It receives two arguments, the catalyst context and the hashref that represents the row. By default, this method is merely a passthrough.
 
 =cut
 
-sub row_format_output { shift; shift; } # passthrough by default
+sub row_format_output
+{
+    my ($self, $c, $row) = @_;
+    return $row; # passthrough by default
+}
 
-=method_protected update_or_create
+=method_protected item
 
- :Private
+item will return a single object called by identifier in the uri. It will be inflated via each_object_inflate.
+
+=cut
+
+sub item
+{
+    my ($self, $c) = @_;
+
+    if($c->req->count_objects != 1)
+    {
+        $c->log->error($_);
+        $self->push_error($c, { message => 'No objects on which to operate' });
+        $c->detach();
+    }
+    else
+    {
+        $c->stash->{response}->{$self->item_root} = $self->each_object_inflate($c, $c->req->get_object(0)->[0]);
+    }
+}
+
+=method_protected update_or_create
 
-update_or_create is responsible for iterating any stored objects and performing updates or creates. Each object is first validated to ensure it meets the criteria specified in the L</create_requires> and L</create_allows> (or L</update_allows>) parameters of the controller config. The objects are then committed within a transaction via L</transact_objects>.
+update_or_create is responsible for iterating any stored objects and performing updates or creates. Each object is first validated to ensure it meets the criteria specified in the L</create_requires> and L</create_allows> (or L</update_allows>) parameters of the controller config. The objects are then committed within a transaction via L</transact_objects> using a closure around L</save_objects>.
 
 =cut
 
-sub update_or_create :Private
+sub update_or_create
 {
-    $DB::single = 1;
     my ($self, $c) = @_;
-    
+
     if($c->req->has_objects)
     {
         $self->validate_objects($c);
-        $self->transact_objects($c, \&save_objects);
+        $self->transact_objects($c, sub { $self->save_objects($c, @_) } );
     }
     else
     {
@@ -467,9 +518,8 @@ transact_objects performs the actual commit to the database via $schema->txn_do.
 
 sub transact_objects
 {
-    $DB::single = 1;
     my ($self, $c, $coderef) = @_;
-    
+
     try
     {
         $self->stored_result_source->schema->txn_do
@@ -494,7 +544,6 @@ This is a shortcut method for performing validation on all of the stored objects
 
 sub validate_objects
 {
-    $DB::single = 1;
     my ($self, $c) = @_;
 
     try
@@ -508,7 +557,7 @@ sub validate_objects
     {
         my $err = $_;
         $c->log->error($err);
-        $err =~ s/\s+at\s+\/.+\n$//g;
+        $err =~ s/\s+at\s+.+\n$//g;
         $self->push_error($c, { message => $err });
         $c->detach();
     }
@@ -522,7 +571,6 @@ validate_object takes the context and the object as an argument. It then filters
 
 sub validate_object
 {
-    $DB::single = 1;
     my ($self, $c, $obj) = @_;
     my ($object, $params) = @$obj;
 
@@ -530,22 +578,22 @@ sub validate_object
     my %requires_map = map
     {
         $_ => 1
-    } 
+    }
     @{
-        ($object->in_storage) 
-        ? [] 
+        ($object->in_storage)
+        ? []
         : $c->stash->{create_requires} || $self->create_requires
     };
-    
+
     my %allows_map = map
     {
         (ref $_) ? %{$_} : ($_ => 1)
-    } 
+    }
     (
-        keys %requires_map, 
+        keys %requires_map,
         @{
-            ($object->in_storage) 
-            ? ($c->stash->{update_allows} || $self->update_allows) 
+            ($object->in_storage)
+            ? ($c->stash->{update_allows} || $self->update_allows)
             : ($c->stash->{create_allows} || $self->create_allows)
         }
     );
@@ -554,22 +602,22 @@ sub validate_object
     {
         # check value defined if key required
         my $allowed_fields = $allows_map{$key};
-        
+
         if (ref $allowed_fields)
         {
             my $related_source = $object->result_source->related_source($key);
             my $related_params = $params->{$key};
             my %allowed_related_map = map { $_ => 1 } @$allowed_fields;
             my $allowed_related_cols = ($allowed_related_map{'*'}) ? [$related_source->columns] : $allowed_fields;
-            
+
             foreach my $related_col (@{$allowed_related_cols})
             {
-                if (my $related_col_value = $related_params->{$related_col}) {
+                if (defined(my $related_col_value = $related_params->{$related_col})) {
                     $values{$key}{$related_col} = $related_col_value;
                 }
             }
         }
-        else 
+        else
         {
             my $value = $params->{$key};
 
@@ -585,7 +633,7 @@ sub validate_object
                     }
                 }
             }
-            
+
             # check for multiple values
             if (ref($value) && !($value == JSON::Any::true || $value == JSON::Any::false))
             {
@@ -599,30 +647,27 @@ sub validate_object
         }
     }
 
-    unless (keys %values || !$object->in_storage) 
+    unless (keys %values || !$object->in_storage)
     {
         die 'No valid keys passed';
     }
 
-    return \%values;  
+    return \%values;
 }
 
 =method_protected delete
 
- :Private
-
-delete operates on the stored objects in the request. It first transacts the objects, deleting them in the database, and then clears the request store of objects.
+delete operates on the stored objects in the request. It first transacts the objects, deleting them in the database using L</transact_objects> and a closure around L</delete_objects>, and then clears the request store of objects.
 
 =cut
 
-sub delete :Private
+sub delete
 {
-    $DB::single = 1;
     my ($self, $c) = @_;
-    
+
     if($c->req->has_objects)
     {
-        $self->transact_objects($c, \&delete_objects);
+        $self->transact_objects($c, sub { $self->delete_objects($c, @_) });
         $c->req->clear_objects;
     }
     else
@@ -633,67 +678,147 @@ sub delete :Private
     }
 }
 
-=head1 HELPER FUNCTIONS
+=method_protected save_objects
 
-This functions are only helper functions and should have a void invocant. If they are called as methods, they will die. The only reason they are stored in the class is to allow for customization without rewriting the methods that make use of these helper functions.
+This method is used by update_or_create to perform the actual database manipulations. It iterates each object calling L</save_object>.
 
-=head2 save_objects
+=cut
 
-This helper function is used by update_or_create to perform the actual database manipulations.
+sub save_objects
+{
+    my ($self, $c, $objects) = @_;
 
-=head2 delete_objects
+    foreach my $obj (@$objects)
+    {
+        $self->save_object($c, $obj);
+    }
+}
 
-This helper function is used by delete to perform the actual database delete of objects.
+=method_protected save_object
+
+save_object first checks to see if the object is already in storage. If so, it calls L</update_object_from_params> otherwise it calls L</insert_object_from_params>
 
 =cut
 
-# NOT A METHOD
-sub save_objects
+sub save_object
 {
-    my ($objects) = @_;
-    die 'save_objects coderef had an invocant and shouldn\'t have had one' if blessed($objects);
+    my ($self, $c, $obj) = @_;
 
-    foreach my $obj (@$objects)
+    my ($object, $params) = @$obj;
+
+    if ($object->in_storage)
     {
-        my ($object, $params) = @$obj;
+        $self->update_object_from_params($c, $object, $params);
+    }
+    else
+    {
+        $self->insert_object_from_params($c, $object, $params);
+    }
 
-        if ($object->in_storage) {
-            foreach my $key (keys %{$params}) {
-                my $value = $params->{$key};
-                if (ref($value) && !($value == JSON::Any::true || $value == JSON::Any::false)) {
-                    my $related_params = delete $params->{$key};
-                    my $row = $object->find_related($key, {} , {});
-                    $row->update($related_params);
-                }
-            }
-            $object->update($params);
-        } else {
-            $object->set_columns($params);
-            $object->insert;
+}
+
+=method_protected update_object_from_params
+
+update_object_from_params iterates through the params to see if any of them are pertinent to relations. If so it calls L</update_object_relation> with the object, and the relation parameters. Then it calls ->upbdate on the object.
+
+=cut
+
+sub update_object_from_params
+{
+    my ($self, $c, $object, $params) = @_;
+
+    foreach my $key (keys %$params)
+    {
+        my $value = $params->{$key};
+        if (ref($value) && !($value == JSON::Any::true || $value == JSON::Any::false))
+        {
+            $self->update_object_relation($c, $object, delete $params->{$key}, $key);
+        }
+    }
+
+    $object->update($params);
+}
+
+=method_protected update_object_relation
+
+update_object_relation finds the relation to the object, then calls ->update with the specified parameters
+
+=cut
+
+sub update_object_relation
+{
+    my ($self, $c, $object, $related_params, $relation) = @_;
+    my $row = $object->find_related($relation, {} , {});
+
+    if ($row) {
+        $row->update($related_params);
+    }
+    else {
+        $object->create_related($relation, $related_params);
+    }
+}
+
+=method_protected insert_object_from_params
+
+insert_object_from_params sets the columns for the object, then calls ->insert
+
+=cut
+
+sub insert_object_from_params
+{
+    my ($self, $c, $object, $params) = @_;
+
+    my %rels;
+    while (my ($k, $v) = each %{ $params }) {
+        if (ref $v && !($v == JSON::Any::true || $v == JSON::Any::false)) {
+            $rels{$k} = $v;
+        }
+        else {
+            $object->set_column($k => $v);
         }
     }
+
+    $object->insert;
+
+    while (my ($k, $v) = each %rels) {
+        $object->create_related($k, $v);
+    }
 }
 
-# NOT A METHOD
+=method_protected delete_objects
+
+delete_objects iterates through each object calling L</delete_object>
+
+=cut
+
 sub delete_objects
 {
-    my ($objects) = @_;
-    die 'delete_objects coderef had an invocant and shouldn\'t have had one' if blessed($objects);
+    my ($self, $c, $objects) = @_;
 
-    map { $_->[0]->delete } @$objects;
+    map { $self->delete_object($c, $_->[0]) } @$objects;
 }
 
-=method_protected end
+=method_protected delete_object
 
- :Private
+Performs the actual ->delete on the object
+
+=cut
+
+sub delete_object
+{
+    my ($self, $c, $object) = @_;
+
+    $object->delete;
+}
+
+=method_protected end
 
 end performs the final manipulation of the response before it is serialized. This includes setting the success of the request both at the HTTP layer and JSON layer. If configured with return_object true, and there are stored objects as the result of create or update, those will be inflated according to the schema and get_inflated_columns
 
 =cut
 
-sub end :Private 
+sub end :Private
 {
-    $DB::single = 1;
     my ($self, $c) = @_;
 
     # check for errors
@@ -711,14 +836,13 @@ sub end :Private
         $c->stash->{response}->{success} = $self->use_json_boolean ? JSON::Any::true : 'true';
         $default_status = 200;
     }
-    
+
     unless ($default_status == 200)
     {
         delete $c->stash->{response}->{$self->data_root};
     }
     elsif($self->return_object && $c->req->has_objects)
     {
-        $DB::single = 1;
         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];
@@ -877,9 +1001,9 @@ Columns and related columns that are okay to search on. For example if only the
 You can also use this to allow custom columns should you wish to allow them through in order to be caught by a custom resultset. For example:
 
   package RestTest::Controller::API::RPC::TrackExposed;
-  
+
   ...
-  
+
   __PACKAGE__->config
     ( ...,
       search_exposes => [qw/position title custom_column/],
@@ -888,9 +1012,9 @@ You can also use this to allow custom columns should you wish to allow them thro
 and then in your custom resultset:
 
   package RestTest::Schema::ResultSet::Track;
-  
+
   use base 'RestTest::Schema::ResultSet';
-  
+
   sub search {
     my $self = shift;
     my ($clause, $params) = @_;
@@ -927,13 +1051,12 @@ For example if you wanted create to return the JSON for the newly created object
     # $c->req->all_objects will contain all of the created
     $self->next::method($c);
 
-    if ($c->req->has_objects) {    
+    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) ] ;
     }
   }
 
-
   package MyApp::Controller::API::RPC::Track;
   ...
   use Moose;
@@ -942,11 +1065,9 @@ For example if you wanted create to return the JSON for the newly created object
 
 It should be noted that the return_object attribute will produce the above result for you, free of charge.
 
-For REST the only difference besides the class names would be that create should be :Private rather than an endpoint.
-
 Similarly you might want create, update and delete to all forward to the list action once they are done so you can refresh your view. This should also be simple enough.
 
-If more extensive customization is required, it is recommened to peer into the roles that comprise the system and make use 
+If more extensive customization is required, it is recommened to peer into the roles that comprise the system and make use
 
 =head1 NOTES