Merge in object_split functionality object_split
nperez [Mon, 1 Mar 2010 02:11:42 +0000 (20:11 -0600)]
.gitignore [new file with mode: 0644]
Changes
lib/Catalyst/Controller/DBIC/API.pm
lib/Catalyst/Controller/DBIC/API/REST.pm
lib/Catalyst/Controller/DBIC/API/RPC.pm
lib/Catalyst/Controller/DBIC/API/Request/Context.pm
lib/Catalyst/Controller/DBIC/API/StaticArguments.pm
t/rest/item.t [new file with mode: 0644]
t/rpc/item.t [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..b18583d
--- /dev/null
@@ -0,0 +1,3 @@
+*.swp
+*.tar.gz
+Catalyst-Controller-DBIC-API*
diff --git a/Changes b/Changes
index 2ab8c02..0c16b47 100644 (file)
--- a/Changes
+++ b/Changes
@@ -4,6 +4,8 @@ Revision history for Catalyst-Controller-DBIC-API: {{ $dist->version }}
 
 - Remove debugging code from tests
 - Fixed some typos and code cleanups
+- Added possibility to fetch a single object by id + tests using 'item'
+- Added item_root attribute which defaults to 'data' and is used as data root for 'item'
 
 2.001003  2010-02-12 19:01:56 America/Chicago
 
index ad36164..ef19f08 100644 (file)
@@ -421,6 +421,31 @@ sub row_format_output
     return $row; # passthrough by default
 }
 
+=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 :Private 
+{
+    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));
+    }
+}
+
+
 =method_protected update_or_create
 
  :Private
index a1c47e1..b40b5de 100644 (file)
@@ -18,8 +18,8 @@ Provides a REST style API interface to the functionality described in L<Catalyst
 
 By default provides the following endpoints:
 
-  $base (accepts PUT and GET)
-  $base/[identifier] (accepts POST and DELETE)
+  $base (operates on lists of objects and accepts GET, PUT, POST and DELETE)
+  $base/[identifier] (operates on a single object and accepts GET, PUT, POST and DELETE)
 
 Where $base is the URI described by L</setup>, the chain root of the controller, and the request type will determine the L<Catalyst::Controller::DBIC::API> method to forward.
 
@@ -36,16 +36,16 @@ As described in L<Catalyst::Controller::DBIC::API/setup>, this action is the cha
        ...
   );
 
-=method_protected base
+=method_protected no_id
 
-Chained: L</setup>
+Chained: L</object_no_id>
 PathPart: none
 CaptureArgs: 0
 
 Forwards to list level methods described in L<Catalyst::Controller::DBIC::API> as follows:
 
-DELETE: forwards to L<Catalyst::Controller::DBIC::API/object> then L<Catalyst::Controller::DBIC::API/delete>
-POST/PUT: forwards to L<Catalyst::Controller::DBIC::API/object> then L<Catalyst::Controller::DBIC::API/update_or_create>
+DELETE: L<Catalyst::Controller::DBIC::API/delete>
+POST/PUT: L<Catalyst::Controller::DBIC::API/update_or_create>
 GET: forwards to L<Catalyst::Controller::DBIC::API/list>
 
 =cut
@@ -76,6 +76,20 @@ sub no_id_GET
        $c->forward('list');
 }
 
+=method_protected with_id
+
+Chained: L</object_with_id>
+PathPart: none
+CaptureArgs: 0
+
+Forwards to list level methods described in L<Catalyst::Controller::DBIC::API> as follows:
+
+DELETE: L<Catalyst::Controller::DBIC::API/delete>
+POST/PUT: L<Catalyst::Controller::DBIC::API/update_or_create>
+GET: forwards to L<Catalyst::Controller::DBIC::API/item>
+
+=cut
+
 sub with_id :Chained('object_with_id') :PathPart('') :ActionClass('REST') :CaptureArgs(0) {}
 
 sub with_id_PUT
index 4fc764a..9655497 100644 (file)
@@ -22,6 +22,7 @@ By default provides the following endpoints:
 
   $base/create
   $base/list
+  $base/id/[identifier]
   $base/id/[identifier]/delete
   $base/id/[identifier]/update
 
@@ -40,14 +41,6 @@ As described in L<Catalyst::Controller::DBIC::API/setup>, this action is the cha
        ...
   );
 
-=method_protected object
-
-Chained: L</setup>
-PathPart: object
-CaptureArgs: 1
-
-Provides an chain point to the functionality described in L<Catalyst::Controller::DBIC::API/object>. All object level endpoints should use this as their chain root.
-
 =cut
 
 sub index : Chained('setup') PathPart('') Args(0) {
@@ -59,7 +52,7 @@ sub index : Chained('setup') PathPart('') Args(0) {
 
 =method_protected create
 
-Chained: L</setup>
+Chained: L</object_no_id>
 PathPart: create
 CaptureArgs: 0
 
@@ -67,17 +60,15 @@ Provides an endpoint to the functionality described in L<Catalyst::Controller::D
 
 =cut
 
-sub create :Chained('setup') :PathPart('create') :Args(0)
+sub create :Chained('object_no_id') :PathPart('create') :Args(0)
 {
        my ($self, $c) = @_;
-    $c->forward('object');
-    return if $self->get_errors($c);
     $c->forward('update_or_create');
 }
 
 =method_protected list
 
-Chained: L</setup>
+Chained: L</deserialize>
 PathPart: list
 CaptureArgs: 0
 
@@ -85,42 +76,90 @@ Provides an endpoint to the functionality described in L<Catalyst::Controller::D
 
 =cut
 
-sub list :Chained('setup') :PathPart('list') :Args(0) {
+sub list :Chained('deserialize') :PathPart('list') :Args(0) {
        my ($self, $c) = @_;
 
         $self->next::method($c);
 }
 
+=method_protected item
+
+Chained: L</object_with_id>
+PathPart: ''
+Args: 0
+
+Provides an endpoint to the functionality described in L<Catalyst::Controller::DBIC::API/item>.
+
+=cut
+
+sub item :Chained('object_with_id') :PathPart('') :Args(0) {
+    my ($self, $c) = @_;
+
+    $c->forward('view');
+}
+
 =method_protected update
 
-Chained: L</object>
+Chained: L</object_with_id>
 PathPart: update
-CaptureArgs: 0
+Args: 0
 
 Provides an endpoint to the functionality described in L<Catalyst::Controller::DBIC::API/update_or_create>.
 
 =cut
 
-sub update :Chained('object') :PathPart('update') :Args(0) {
-       my ($self, $c) = @_;
+sub update :Chained('object_with_id') :PathPart('update') :Args(0) {
+    my ($self, $c) = @_;
 
     $c->forward('update_or_create');
 }
 
 =method_protected delete
 
-Chained: L</object>
+Chained: L</object_with_id>
 PathPart: delete
-CaptureArgs: 0
+Args: 0
 
 Provides an endpoint to the functionality described in L<Catalyst::Controller::DBIC::API/delete>.
 
 =cut
 
-sub delete :Chained('object') :PathPart('delete') :Args(0) {
-       my ($self, $c) = @_;
+sub delete :Chained('object_with_id') :PathPart('delete') :Args(0)
+{
+    my ($self, $c) = @_;
+    $self->next::method($c);
+}
 
-        $self->next::method($c);
+=method_protected update_bulk
+
+Chained: L</object_no_id>
+PathPart: update
+Args: 0
+
+Provides an endpoint to the functionality described in L<Catalyst::Controller::DBIC::API/update_or_create> for multiple objects.
+
+=cut
+
+sub update_bulk :Chained('object_no_id') :PathPart('update') :Args(0)
+{
+    my ($self, $c) = @_;
+    $c->forward('update_or_create');
+}
+
+=method_protected delete_bulk
+
+Chained: L</object_no_id>
+PathPart: delete
+Args: 0
+
+Provides an endpoint to the functionality described in L<Catalyst::Controller::DBIC::API/delete> for multiple objects.
+
+=cut
+
+sub delete_bulk :Chained('object_no_id') :PathPart('delete') :Args(0)
+{
+    my ($self, $c) = @_;
+    $self->next::method($c);
 }
 
 1;
index 5653031..9f9ea72 100644 (file)
@@ -32,6 +32,7 @@ has objects =>
         count_objects => 'count',
         has_objects => 'count',
         clear_objects => 'clear',
+        get_object => 'get',
     },
 );
 
index 063b75f..7b972b3 100644 (file)
@@ -130,6 +130,15 @@ data_root controls how to reference where the data is in the the request_data
 
 has 'data_root' => ( is => 'ro', isa => Str, default => 'list');
 
+=attribute_public item_root is: ro, isa: Str, default: 'data'
+
+item_root controls how to reference where the data for single object
+requests is in the the request_data
+
+=cut
+
+has 'item_root' => ( is => 'ro', isa => Str, default => 'data');
+
 =attribute_public total_entries_arg is: ro, isa: Str, default: 'totalcount'
 
 total_entries_arg controls how to reference 'total_entries' in the the request_data
diff --git a/t/rest/item.t b/t/rest/item.t
new file mode 100644 (file)
index 0000000..a376a6b
--- /dev/null
@@ -0,0 +1,43 @@
+use 5.6.0;
+
+use strict;
+use warnings;
+
+use lib 't/lib';
+
+my $base = 'http://localhost';
+
+use RestTest;
+use DBICTest;
+use URI;
+use Test::More;
+use Test::WWW::Mechanize::Catalyst 'RestTest';
+use HTTP::Request::Common;
+use JSON::Any;
+
+my $mech = Test::WWW::Mechanize::Catalyst->new;
+ok(my $schema = DBICTest->init_schema(), 'got schema');
+
+my $artist_view_url = "$base/api/rest/artist/";
+
+{
+    my $id = 1;
+    my $req = GET( $artist_view_url . $id, undef, 'Accept' => 'application/json' );
+    $mech->request($req);
+    cmp_ok( $mech->status, '==', 200, 'open attempt okay' );
+    my %expected_response = $schema->resultset('Artist')->find($id)->get_columns;
+    my $response = JSON::Any->Load( $mech->content);
+    is_deeply( $response, { data => \%expected_response, success => 'true' }, 'correct data returned' );
+}
+
+{
+    my $id = 5;
+    my $req = GET( $artist_view_url . $id, undef, 'Accept' => 'application/json' );
+    $mech->request($req);
+    cmp_ok( $mech->status, '==', 400, 'open attempt not ok' );
+    my $response = JSON::Any->Load( $mech->content);
+    is($response->{success}, 'false', 'not existing object fetch failed ok');
+    like($response->{messages}->[0], qr/^No object found for id/, 'error message for not existing object fetch ok');
+}
+
+done_testing();
diff --git a/t/rpc/item.t b/t/rpc/item.t
new file mode 100644 (file)
index 0000000..538c379
--- /dev/null
@@ -0,0 +1,43 @@
+use 5.6.0;
+
+use strict;
+use warnings;
+
+use lib 't/lib';
+
+my $base = 'http://localhost';
+
+use RestTest;
+use DBICTest;
+use URI;
+use Test::More;
+use Test::WWW::Mechanize::Catalyst 'RestTest';
+use HTTP::Request::Common;
+use JSON::Any;
+
+my $mech = Test::WWW::Mechanize::Catalyst->new;
+ok(my $schema = DBICTest->init_schema(), 'got schema');
+
+my $artist_view_url = "$base/api/rpc/artist/id/";
+
+{
+    my $id = 1;
+    my $req = GET( $artist_view_url . $id, undef, 'Accept' => 'application/json' );
+    $mech->request($req);
+    cmp_ok( $mech->status, '==', 200, 'open attempt okay' );
+    my %expected_response = $schema->resultset('Artist')->find($id)->get_columns;
+    my $response = JSON::Any->Load( $mech->content);
+    is_deeply( $response, { data => \%expected_response, success => 'true' }, 'correct data returned' );
+}
+
+{
+    my $id = 5;
+    my $req = GET( $artist_view_url . $id, undef, 'Accept' => 'application/json' );
+    $mech->request($req);
+    cmp_ok( $mech->status, '==', 400, 'open attempt not ok' );
+    my $response = JSON::Any->Load( $mech->content);
+    is($response->{success}, 'false', 'not existing object fetch failed ok');
+    like($response->{messages}->[0], qr/^No object found for id/, 'error message for not existing object fetch ok');
+}
+
+done_testing();