File Coverage

blib/lib/Clustericious/RouteBuilder/CRUD.pm
Criterion Covered Total %
statement 43 111 38.7
branch 6 38 15.7
condition 3 20 15.0
subroutine 9 15 60.0
pod n/a
total 61 184 33.1


line stmt bran cond sub pod time code
1             package Clustericious::RouteBuilder::CRUD;
2              
3 4     4   1954 use strict;
  4         9  
  4         153  
4 4     4   22 use warnings;
  4         9  
  4         99  
5 4     4   23 use Clustericious::Log;
  4         8  
  4         30  
6 4     4   3006 use Data::Dumper;
  4         11  
  4         446  
7              
8             # ABSTRACT: build crud routes easily
9             our $VERSION = '1.27'; # VERSION
10              
11              
12 4         54 use Sub::Exporter -setup => {
13             exports => [
14             "create" => \&_build_create,
15             "read" => \&_build_read,
16             "update" => \&_build_update,
17             "delete" => \&_build_delete,
18             "list" => \&_build_list,
19             ],
20             collectors => ['defaults'],
21 4     4   112 };
  4         28842  
22              
23             sub _build_create {
24 3     3   205 my ($class, $name, $arg, $defaults) = @_;
25 3   50     31 my $finder = $arg->{finder} || $defaults->{defaults}{finder} || die "no finder defined";
26 3 50       31 $finder->can("find_class") or die "$finder must be able to find_class";
27             return sub {
28 6     6   336 my $self = shift;
29 6         24 $self->app->log->info("called do_create");
30 6         161 my $table = $self->stash->{table};
31 6         76 TRACE "create $table";
32 6         108 $self->parse_autodata;
33 6         567 my $object_class = $finder->find_class($table);
34 6         20 TRACE "data : ".Dumper($self->stash("autodata"));
35 6         53 my $object = $object_class->new(%{$self->stash->{autodata}});
  6         24  
36 6 50 33     38 if ($self->param("skip_existing") && $object->load(speculative => 1)) {
37 0         0 DEBUG "Found existing $table, not saving";
38 0         0 $self->stash(autodata => { text => "skipped" });
39 0         0 return;
40             }
41 6 50       1192 $object->save or LOGDIE( $object->errors );
42 6 50       22 $object->load or LOGDIE "Could not reload object : ".$object->errors;
43 6         20 $self->stash(autodata => $object->as_hash);
44 3         19 };
45             }
46              
47             sub _build_read {
48 2     2   451 my ($class, $name, $arg, $defaults) = @_;
49 2   50     18 my $finder = $arg->{finder} || $defaults->{defaults}{finder} || die "no finder defined";
50 2 50       33 $finder->can("find_object") or die "$finder must be able to find_object";
51             sub {
52 8     8   430 my $self = shift;
53 8         101 my $table = $self->stash->{table};
54 8         75 my @keys = split /\//, $self->stash->{key};
55 8         105 TRACE "read $table (@keys)";
56 8 50       81 my $obj = $finder->find_object($table,@keys)
57             or return $self->reply->not_found( join '/',$table,@keys );
58 8         28 $self->app->log->debug("Viewing $table @keys");
59              
60 8         215 $self->stash(autodata => $obj->as_hash);
61              
62 2         19 };
63             }
64              
65             sub _build_delete {
66 0     0     my ($class, $name, $arg, $defaults) = @_;
67 0   0       my $finder = $arg->{finder} || $defaults->{defaults}{finder} || die "no finder defined";
68 0 0         $finder->can("find_object") or die "$finder must be able to find_object";
69             sub {
70 0     0     my $self = shift;
71 0           my $table = $self->stash->{table};
72 0           my @keys = split /\//, $self->stash->{key};
73 0           TRACE "delete $table (@keys)";
74 0 0         my $obj = $finder->find_object($table,@keys)
75             or return $self->reply->not_found( join '/',$table,@keys );
76 0           $self->app->log->debug("Deleting $table @keys");
77              
78 0 0         $obj->delete or LOGDIE($obj->errors);
79 0           $self->stash->{text} = "ok";
80             }
81 0           }
82              
83             sub _build_update {
84 0     0     my ($class, $name, $arg, $defaults) = @_;
85              
86             my $finder = $arg->{finder} || $defaults->{defaults}{finder}
87 0   0       || die "no finder defined";
88              
89 0 0         $finder->can("find_object") or die "$finder must be able to find_object";
90              
91             sub {
92 0     0     my $self = shift;
93 0           my $table = $self->stash->{table};
94 0           my @keys = split /\//, $self->stash->{key};
95              
96 0 0         my $obj = $finder->find_object($table, @keys)
97             or return $self->reply->not_found( join '/',$table,@keys );
98              
99 0           TRACE "Updating $table @keys";
100 0           $self->parse_autodata;
101              
102 0           my $pkeys = $obj->meta->primary_key_column_names;
103 0           my $ukeys = $obj->meta->unique_keys_column_names;
104 0           my $columns = $obj->meta->column_names;
105 0           my $nested = $obj->nested_tables;
106              
107 0           while (my ($key, $value) = each %{$self->stash->{autodata}})
  0            
108             {
109 0 0         next if grep { $key eq $_ } @$pkeys, @$ukeys; # Skip key fields
  0            
110              
111             LOGDIE("Can't update $key in $table (only @$columns, @$nested)")
112 0 0         unless grep { $key eq $_ } @$columns, @$nested;
  0            
113              
114 0           TRACE "Setting $key to $value for $table @keys";
115 0 0         $obj->$key($value) or LOGDIE($obj->errors);
116             }
117              
118 0 0         $obj->save or LOGDIE($obj->errors);
119              
120 0           $self->stash->{autodata} = $obj->as_hash;
121 0           };
122             }
123              
124             sub _build_list {
125 0     0     my ($class, $name, $arg, $defaults) = @_;
126 0   0       my $finder = $arg->{finder} || $defaults->{defaults}{finder} || die "no finder defined";
127 0 0         $finder->can("find_object") or die "$finder must be able to find_object";
128             sub {
129 0     0     my $self = shift;
130 0           my $table = $self->stash('table');
131 0           my $params = $self->stash('params');
132              
133             # Use http range header for limit and offset.
134 0           my %range;
135 0 0         if (my $range = $self->req->headers->range) {
136 0           my ($items) = $range =~ /^items=(.*)$/;
137 0           my ($first,$last) = $items =~ /^(\d+)-(\d+)$/;
138 0 0 0       if (defined($first) && defined($last)) {
139 0           %range = ( offset => $first - 1, limit => ($last-$first+1) );
140             } else {
141 0           WARN "Unrecognized range header : $range";
142 0           %range = (limit => 10);
143             }
144             } else {
145 0           %range = (limit => 10);
146             }
147              
148 0           $self->app->log->debug("Listing $table");
149 0 0         my $object_class = $finder->find_class($table)
150             or return $self->reply->not_found( $table );
151 0           my $pkey = $object_class->meta->primary_key;
152 0           my $manager = $object_class . '::Manager';
153              
154 0           my $objectlist = $manager->get_objects(
155             object_class => $object_class,
156             select => [ $pkey->columns ],
157             sort_by => [ $pkey->columns ],
158             %range);
159              
160             # Return total count in "content-range".
161 0           my $count = $manager->get_objects_count( object_class => $object_class );
162             $self->res->headers->content_range(
163             sprintf( "items %d-%d/%d",
164             ( 1 + ($range{offset} || 0)),
165 0   0       ( ($range{offset} || 0) + @$objectlist ),
      0        
166             $count )
167             );
168              
169 0           my @l;
170              
171 0           foreach my $obj (@$objectlist) {
172 0           push(@l, join('/', map { $obj->$_ } $pkey->columns ));
  0            
173             }
174              
175 0           $self->stash(autodata => \@l);
176 0           $self->res->code(206); # "Partial content"
177 0           };
178             }
179              
180             1;
181              
182             __END__
183              
184             =pod
185              
186             =encoding UTF-8
187              
188             =head1 NAME
189              
190             Clustericious::RouteBuilder::CRUD - build crud routes easily
191              
192             =head1 VERSION
193              
194             version 1.27
195              
196             =head1 SYNOPSIS
197              
198             use My::Object::Class;
199             use Clustericious::RouteBuilder;
200             use Clustericious::RouteBuilder::CRUD
201             "create" => { -as => "do_create" },
202             "read" => { -as => "do_read" },
203             "delete" => { -as => "do_delete" },
204             "update" => { -as => "do_update" },
205             "list" => { -as => "do_list" },
206             defaults => { finder => "My::Finder::Class" },
207             ;
208              
209             ...
210              
211             post => "/:table" => \&do_create;
212              
213             =head1 DESCRIPTION
214              
215             This package provides some handy subroutines for building CRUD
216             routes in your clustericious application.
217              
218             The class referenced by "finder" must have methods named
219             find_class and find_object.
220              
221             The objects returned by find_object must have a method named as_hash.
222              
223             =head1 SUPER CLASS
224              
225             none
226              
227             =head1 SEE ALSO
228              
229             L<Clustericious>
230              
231             =head1 AUTHOR
232              
233             Original author: Brian Duggan
234              
235             Current maintainer: Graham Ollis E<lt>plicease@cpan.orgE<gt>
236              
237             Contributors:
238              
239             Curt Tilmes
240              
241             Yanick Champoux
242              
243             =head1 COPYRIGHT AND LICENSE
244              
245             This software is copyright (c) 2013 by NASA GSFC.
246              
247             This is free software; you can redistribute it and/or modify it under
248             the same terms as the Perl 5 programming language system itself.
249              
250             =cut