File Coverage

blib/lib/PONAPI/DAO.pm
Criterion Covered Total %
statement 60 60 100.0
branch 4 4 100.0
condition 2 2 100.0
subroutine 23 23 100.0
pod 9 10 90.0
total 98 99 98.9


line stmt bran cond sub pod time code
1             # ABSTRACT: Data Abstraction Object class
2             package PONAPI::DAO;
3              
4 8     8   293727 use Moose;
  8         1873450  
  8         67  
5              
6 8     8   62242 use PONAPI::DAO::Request::Retrieve;
  8         31  
  8         343  
7 8     8   4395 use PONAPI::DAO::Request::RetrieveAll;
  8         30  
  8         365  
8 8     8   4368 use PONAPI::DAO::Request::RetrieveRelationships;
  8         47  
  8         374  
9 8     8   4378 use PONAPI::DAO::Request::RetrieveByRelationship;
  8         34  
  8         361  
10 8     8   4437 use PONAPI::DAO::Request::Create;
  8         34  
  8         322  
11 8     8   4254 use PONAPI::DAO::Request::CreateRelationships;
  8         36  
  8         367  
12 8     8   4306 use PONAPI::DAO::Request::Update;
  8         33  
  8         348  
13 8     8   4160 use PONAPI::DAO::Request::UpdateRelationships;
  8         78  
  8         356  
14 8     8   4323 use PONAPI::DAO::Request::Delete;
  8         28  
  8         327  
15 8     8   4060 use PONAPI::DAO::Request::DeleteRelationships;
  8         34  
  8         3270  
16              
17             has repository => (
18             is => 'ro',
19             does => 'PONAPI::Repository',
20             required => 1,
21             );
22              
23             has version => (
24             is => 'ro',
25             isa => 'Str',
26             required => 1,
27             );
28              
29             has json => (
30             is => 'ro',
31             isa => JSON::MaybeXS::JSON(),
32             default => sub { JSON::MaybeXS->new->allow_nonref->utf8->canonical },
33             );
34              
35 32     32 1 107823 sub retrieve_all { shift->_action( 'PONAPI::DAO::Request::RetrieveAll' , @_ ) }
36 55     55 1 97356 sub retrieve { shift->_action( 'PONAPI::DAO::Request::Retrieve' , @_ ) }
37 16     16 1 34569 sub retrieve_relationships { shift->_action( 'PONAPI::DAO::Request::RetrieveRelationships' , @_ ) }
38 15     15 0 35199 sub retrieve_by_relationship { shift->_action( 'PONAPI::DAO::Request::RetrieveByRelationship' , @_ ) }
39 27     27 1 68154 sub create { shift->_action( 'PONAPI::DAO::Request::Create' , @_ ) }
40 21     21 1 41914 sub create_relationships { shift->_action( 'PONAPI::DAO::Request::CreateRelationships' , @_ ) }
41 26     26 1 57126 sub update { shift->_action( 'PONAPI::DAO::Request::Update' , @_ ) }
42 15     15 1 29893 sub update_relationships { shift->_action( 'PONAPI::DAO::Request::UpdateRelationships' , @_ ) }
43 20     20 1 43406 sub delete : method { shift->_action( 'PONAPI::DAO::Request::Delete' , @_ ) }
44 16     16 1 42027 sub delete_relationships { shift->_action( 'PONAPI::DAO::Request::DeleteRelationships' , @_ ) }
45              
46             sub _action {
47 243     243   498 my $self = shift;
48 243         466 my $action_class = shift;
49              
50 243 100       1224 my $ponapi_parameters = @_ == 1 ? $_[0] : +{ @_ };
51 243         8504 $ponapi_parameters->{repository} = $self->repository;
52 243         6311 $ponapi_parameters->{version} = $self->version;
53 243         6331 $ponapi_parameters->{json} = $self->json;
54              
55 243         479 local $@;
56 243         425 my @ret;
57             eval {
58 243         9069 @ret = $action_class->new($ponapi_parameters)->execute();
59 196         236065 1;
60 243 100       454 } or do {
61 47   100     2063 my $e = $@ || 'Unknown error';
62 47         356 @ret = PONAPI::Exception
63             ->new_from_exception($e)
64             ->as_response;
65             };
66              
67 243         51019 return @ret;
68             }
69              
70             __PACKAGE__->meta->make_immutable;
71 8     8   84 no Moose; 1;
  8         17  
  8         58  
72              
73             __END__
74              
75             =pod
76              
77             =encoding UTF-8
78              
79             =head1 NAME
80              
81             PONAPI::DAO - Data Abstraction Object class
82              
83             =head1 VERSION
84              
85             version 0.003003
86              
87             =head1 SYNOPSIS
88              
89             use PONAPI::DAO;
90             my $dao = PONAPI::DAO->new( repository => $repository );
91              
92             my ($status, $doc) = $dao->retrieve( type => $type, id => $id );
93             die "retrieve failed; status $status, $doc->{errors}[0]{detail}"
94             if $doc->{errors};
95              
96             use Data::Dumper;
97             say Dumper($doc->{data});
98              
99             # Fetch all resources of this type
100             $dao->retrieve_all( type => $type );
101              
102             # Fetch all the relationships of $rel_type for the requested resource
103             $dao->retrieve_relationships(
104             type => $type,
105             id => $id,
106             rel_type => $rel_type,
107             );
108              
109             # Like the above, but fetches full resources instead of just relationships
110             $dao->retrieve_by_relationship(
111             type => $type,
112             id => $id,
113             rel_type => $rel_type,
114             );
115              
116             # Create a new resource
117             $dao->create(
118             type => $type,
119             data => {
120             type => $type,
121             attributes => { ... },
122             relationships => { ... },
123             }
124             );
125              
126             # *Add* a new entry to the relationships between $type and $rel_type
127             $dao->create_relationsips(
128             type => $type,
129             rel_type => $rel_type,
130             data => [
131             { ... },
132             ]
133             );
134              
135             # Update the attributes and/or relationships of a resource
136             $dao->update(
137             type => $type,
138             id => $id,
139             data => {
140             type => $type,
141             id => $id,
142             attributes => { ... },
143             relationships => { ... },
144             },
145             );
146              
147             # Update the relationships of a given type for one resource
148             $dao->update_relationships(
149             type => $type,
150             id => $id,
151             rel_type => $rel_type,
152             data => $update_data,
153             );
154              
155             # Delete a resource
156             $dao->delete(
157             type => $type,
158             id => $id,
159             );
160              
161             # Delete the members from the relationship
162             $dao->delete_relationships(
163             type => $type,
164             id => $id,
165             rel_type => $rel_type,
166             data => [
167             { ... }, ...
168             ],
169             );
170              
171             =head1 DESCRIPTION
172              
173             Data Access Object for the JSON API. This sits in between a server
174             and a L<repository|"PONAPI::Repository">.
175              
176             All public DAO methods will return a 3-item list of a status, headers,
177             and the response body; this can then be fed directly to a PSGI application:
178              
179             If present, the C<data> key of the response will contain either a
180             resource, or an arrayref of resources. Resources are represented as
181             plain hashrefs, and they B<must> include both a C<type> and C<id>; they
182             may also contain additional keys. See L<http://jsonapi.org/format/#document-resource-objects>
183             for a more in-depth description.
184              
185             =head1 METHODS
186              
187             =head2 new
188              
189             Create a new instance of PONAPI::DAO.
190              
191             my $DAO = PONAPI::DAO->new(
192             repository => $repository,
193             );
194              
195             Where C<$repository> implements the L<PONAPI::Repository> role.
196              
197             As expanded below in L</"Return value of update operations">, the JSON API specification requires some
198             update operations returning C<200 OK> to also do a C<retrieve> and include
199             it in the response.
200             By default, C<PONAPI::DAO> will simply turn those C<200 OK> into
201             C<202 Accepted>, avoiding the need to do the extra fetch. If needed, the
202             full 200 responses can be re-enabled by passing
203             C<respond_to_updates_with_200 =E<gt> 1,> to C<new>.
204              
205             =head1 API METHODS
206              
207             With the exception of C<create> and C<retrieve_all>, the type and id arguments are mandatory
208             for all operations.
209              
210             =head2 retrieve
211              
212             Retrieve a resource. Returns both the status of the request and
213             the document to be encoded.
214              
215             my ( $status, $doc ) = $dao->retrieve( type => "articles", id => 1 );
216              
217             if ( $doc->{errors} ) {
218             die "Welp! Got some errors: ", join "\n",
219             map $_->{detail}, @{ $doc->{errors} };
220             }
221              
222             say $doc->{data}{attributes}{title};
223              
224             This accepts several optional values:
225              
226             =over 4
227              
228             =item fields
229              
230             Allows fetching only specific fields of the resource:
231              
232             # This will fetch the entire resource
233             $dao->retrieve(type => "articles", id => 1);
234              
235             # This will only fetch the title attribute
236             $dao->retrieve(
237             type => "articles",
238             id => 1,
239             fields => { articles => [qw/ title /] },
240             );
241              
242             Note how the fields fetched are requested per attribute type. This allows you to
243             request specific fields in resources fetched through C<include>.
244              
245             =item include
246              
247             Allows including related resources.
248              
249             # The response will contain a top-level 'include' key with the
250             # article's author
251             $dao->retrieve(type => "articles", id => 1, include => [qw/ author /]);
252              
253             # We can combine include with C<fields> to fetch just the author's name:
254             my $response = $dao->retrieve(
255             id => 1,
256             type => "articles",
257             include => [qw/ author /],
258             fields => { author => [qw/ name /] }
259             );
260              
261             These will show up in the document in the top-level "included" key.
262              
263             =item page
264              
265             Used to provide pagination information to the underlaying repository.
266             Each implementation may provide a different pagination strategy.
267              
268             =item filter
269              
270             Entirely implementation-specific.
271              
272             =back
273              
274             =head2 retrieve_all
275              
276             As you might expect, this is similar to C<retrieve>. The returned document
277             will contain an arrayref of resource, rather than a single resource.
278              
279             Depending on the implementation, you may be able to combine this with
280             C<filter> to retrieve multiple specific resources in a single request.
281              
282             C<retrieve_all> takes all the same optional arguments as C<retrieve>,
283             plus one of its own:
284              
285             =over 1
286              
287             =item sort
288              
289             Sorting strategy for the request. Implementation-specific.
290              
291             =back
292              
293             =head2 retrieve_relationships
294              
295             This retrieves all relationships of C<$type>. Will return either an
296             arrayref or a hashref, depending on whether the requested relationship is
297             one-to-one or one-to-many:
298              
299             # Retrieves all comments made for an article
300             $doc = $dao->retrieve_relationships(
301             type => "articles",
302             id => 1,
303             rel_type => "comments",
304             );
305             # articles-to-comments is one-to-many, so it returns an arrayref
306             say scalar @{ $doc->{data} };
307              
308             $doc = $dao->retrieve_relationships(
309             type => "articles",
310             id => 1,
311             rel_type => "author",
312             );
313             # articles-to-author is one-to-one, so it returns a hashref
314             say $doc->{data}{id};
315              
316             Takes two optional arguments, C<filter> and C<page>; both are entirely
317             implementation specific.
318              
319             =head2 retrieve_by_relationships
320              
321             Like C<retrieve_relationships>, but fetches full resources, rather than
322             identifier objects.
323              
324             # One-to-many relationship, this returns an arrayref of resource hashrefs.
325             $dao->retrieve_by_relationships(
326             type => "articles",
327             id => 1,
328             rel_type => "comments",
329             );
330             # Same as:
331             $doc = $dao->retrieve_relationships(
332             type => "articles",
333             id => 1,
334             rel_type => "comments",
335             );
336             $comments = $dao->retrieve_all(
337             type => $doc->{data}{type},
338             filter => { id => [ map $_->{id}, @{ $doc->{data} } ] },
339             );
340              
341             # One-to-one relationship
342             $doc = $dao->retrieve_relationships(
343             type => "articles",
344             id => 1,
345             rel_type => "author",
346             );
347             # Same as:
348             $doc = $dao->retrieve_relationships(
349             type => "articles",
350             id => 1,
351             rel_type => "author",
352             );
353             $author = $dao->retrieve(
354             type => $doc->{data}{type},
355             id => $doc->{data}{id},
356             );
357              
358             Takes the same optional arguments as C<retrieve> and C<retrieve_all>, whichever is applicable.
359              
360             =head2 delete
361              
362             Deletes a resource.
363              
364             $dao->delete( type => "articles", id => 1 );
365              
366             May or may not return a document with a top-level meta key.
367              
368             =head2 create
369              
370             Creates a resource.
371              
372             $dao->create(
373             type => "articles",
374             data => {
375             type => "articles",
376             attributes => { ... },
377             relationships => { ... },
378             },
379             );
380              
381             This is one of the few methods where the C<id> is optional. If provided, the underlaying
382             implementation may choose to use it, instead of generating a new idea for the created resource.
383              
384             If successful, the response will include both a C<Location> header specifying
385             where the new resource resides, and a document that includes the newly
386             created resource.
387              
388             =head2 update
389              
390             Updates a resource. This can be used to either update the resource attributes,
391             or its relationships; for the latter, you may want to consider using C<update_relationships>,
392             C<create_relationships>, or C<delete_relationships> instead.
393              
394             # Change article's title
395             $dao->update(
396             type => "articles",
397             id => 1,
398             data => {
399             type => "articles",
400             id => 1,
401             attributes => { title => "Updated title!" },
402             }
403             );
404              
405             # Change the article's author
406             $dao->update(
407             type => "articles",
408             id => 1,
409             data => {
410             type => "articles",
411             id => 1,
412             relationships => {
413             author => { type => "people", id => 99 },
414             },
415             },
416             );
417              
418             # Switch the tags of the article to a new set of tags
419             $dao->update(
420             type => "articles",
421             id => 1,
422             data => {
423             type => "articles",
424             id => 1,
425             relationships => {
426             tags => [
427             { type => "tag", id => 4 },
428             { type => "tag", id => 5 },
429             ],
430             },
431             },
432             );
433              
434             Missing attributes or relationships will B<not> be modified.
435              
436             =head3 Return value of update operations
437              
438             C<update>, C<delete_relationships>, C<create_relationships>, and
439             C<update_relationships> all follow the same rules for their responses.
440              
441             If successful, they will return with either:
442              
443             =over 3
444              
445             =item 200 OK
446              
447             If the update was successful and no extra data was updated, the response
448             will include a top-level C<meta> key, with a description of what was
449             updated.
450              
451             Meanwhile, if the update was successful but more data than requested was
452             updated -- Consider C<updated-at> columns in a table -- then the request
453             will return both a top-level C<meta> key, and a top-level C<data> key,
454             containing the results of a C<retrieve> operation on the primary updated
455             resource. Since this behavior can be undesirable, unless C<PONAPI::DAO-E<gt>new>
456             was passed C<respond_to_updates_with_200 =E<gt> 1>, this sort of response is
457             disabled, and the server will instead respond with a L</"202 Accepted">,
458             described below.
459              
460             =item 202 Accepted
461              
462             The response will include a top-level C<meta> key, with a human-readable
463             description of the success.
464              
465             This is used when the server accepted the operation, but hasn't yet
466             completed it; "Completed" being purposely very ambiguous. In a SQL-based
467             implementation, it might simply mean that the change hasn't fully replicated
468             yet.
469              
470             =item 204 No Content
471              
472             If the operation was successful and nothing beyond the requested was modified,
473             the server may choose to send a 204 with no body, instead of a 200.
474              
475             =back
476              
477             =head2 delete_relationships
478              
479             Remove members from a one-to-many relationship.
480              
481             # Remove two comments from the article
482             $dao->delete(
483             type => "articles",
484             id => 1,
485             rel_type => "comments',
486             data => [
487             { type => "comment", id => 44 },
488             { type => "comment", id => 89 },
489             ],
490             );
491              
492             See also L</"Return value of update operations">.
493              
494             =head2 update_relationships
495              
496             Update the relationships of C<$rel_type>; this will replace all relationships
497             of the requested type with the ones provided. Note that different semantics
498             are used for one-to-one and one-to-many relationships:
499              
500             # Replace all comments
501             $dao->update_relationships(
502             type => "articles",
503             id => 1,
504             rel_type => "comments",
505             # articles-to-comments is one-to-many, so it gets an arrayref
506             data => [ { ... }, { ... } ],
507             );
508              
509             # Change the author of the article
510             $dao->update_relationships(
511             type => "articles",
512             id => 1,
513             rel_type => "author",
514             # articles-to-authors is one-to-one, so it gets a simple hashref
515             data => { type => "people", id => 42 },
516             );
517              
518             # Clear the comments of an article
519             $dao->update_relationships(
520             type => "articles",
521             id => 1,
522             rel_type => "comments",
523             # Empty array to clear out a one-to-many
524             data => [],
525             );
526              
527             # Clear the author of the relationship
528             $dao->update_relationships(
529             type => "articles",
530             id => 1,
531             rel_type => "author",
532             # undef to clear out one-to-one
533             data => undef,
534             );
535              
536             See also L</"Return value of update operations">.
537              
538             =head2 create_relationships
539              
540             Adds a new member to the specified one-to-many relationship.
541              
542             # Add a new, existing comment to the article
543             $dao->create_relationships(
544             type => "articles",
545             id => 1,
546             rel_type => "comments",
547             data => [
548             { type => "comment", id => 55 },
549             ],
550             );
551              
552             See also L</"Return value of update operations">.
553              
554             =head1 AUTHORS
555              
556             =over 4
557              
558             =item *
559              
560             Mickey Nasriachi <mickey@cpan.org>
561              
562             =item *
563              
564             Stevan Little <stevan@cpan.org>
565              
566             =item *
567              
568             Brian Fraser <hugmeir@cpan.org>
569              
570             =back
571              
572             =head1 COPYRIGHT AND LICENSE
573              
574             This software is copyright (c) 2019 by Mickey Nasriachi, Stevan Little, Brian Fraser.
575              
576             This is free software; you can redistribute it and/or modify it under
577             the same terms as the Perl 5 programming language system itself.
578              
579             =cut