File Coverage

blib/lib/App/Dochazka/REST/Model/Component.pm
Criterion Covered Total %
statement 48 110 43.6
branch 0 26 0.0
condition 0 17 0.0
subroutine 16 26 61.5
pod 10 10 100.0
total 74 189 39.1


line stmt bran cond sub pod time code
1             # *************************************************************************
2             # Copyright (c) 2014-2017, SUSE LLC
3             #
4             # All rights reserved.
5             #
6             # Redistribution and use in source and binary forms, with or without
7             # modification, are permitted provided that the following conditions are met:
8             #
9             # 1. Redistributions of source code must retain the above copyright notice,
10             # this list of conditions and the following disclaimer.
11             #
12             # 2. Redistributions in binary form must reproduce the above copyright
13             # notice, this list of conditions and the following disclaimer in the
14             # documentation and/or other materials provided with the distribution.
15             #
16             # 3. Neither the name of SUSE LLC nor the names of its contributors may be
17             # used to endorse or promote products derived from this software without
18             # specific prior written permission.
19             #
20             # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21             # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22             # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23             # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24             # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25             # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26             # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27             # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28             # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29             # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30             # POSSIBILITY OF SUCH DAMAGE.
31             # *************************************************************************
32              
33              
34             use 5.012;
35 41     41   1706 use strict;
  41         145  
36 41     41   200 use warnings;
  41         79  
  41         742  
37 41     41   180 use App::CELL qw( $CELL $log $meta $site );
  41         76  
  41         1185  
38 41     41   212 use App::Dochazka::REST::Mason qw( $comp_root $interp );
  41         88  
  41         4335  
39 41     41   14353 use App::Dochazka::REST::Model::Shared qw( cud load load_multiple priv_by_eid );
  41         124  
  41         4063  
40 41     41   1324 use DBI;
  41         97  
  41         2147  
41 41     41   244 use File::Path;
  41         88  
  41         1216  
42 41     41   213 use File::Spec;
  41         94  
  41         1600  
43 41     41   219 use JSON;
  41         74  
  41         705  
44 41     41   187 use Params::Validate qw{:all};
  41         95  
  41         305  
45 41     41   4119 use Try::Tiny;
  41         95  
  41         4629  
46 41     41   286  
  41         112  
  41         1872  
47             # we get 'spawn', 'reset', and accessors from parent
48             use parent 'App::Dochazka::Common::Model::Component';
49 41     41   259  
  41         85  
  41         334  
50              
51              
52              
53             =head1 NAME
54              
55             App::Dochazka::REST::Model::Component - component class
56              
57              
58              
59              
60             =head1 SYNOPSIS
61              
62             use App::Dochazka::REST::Model::Component;
63              
64             ...
65              
66              
67             =head1 DATA MODEL
68              
69             =head2 Components in the database
70              
71              
72             CREATE TABLE components (
73             cid serial PRIMARY KEY,
74             path varchar(2048) UNIQUE NOT NULL,
75             source text NOT NULL,
76             acl varchar(16) NOT NULL,
77             validations textj
78             )
79              
80              
81              
82             =head2 Components in the Perl API
83              
84             =over
85              
86             =item * constructor (L<spawn>)
87              
88             =item * basic accessors (L<cid>, L<path>, L<source>, L<acl>, L<validations>)
89              
90             =item * L<reset> (recycles an existing object by setting it to desired state)
91              
92             =item * L<TO_JSON> (returns 'unblessed' version of an Activity object)
93              
94             =item * L<compare> (compare two objects)
95              
96             =item * L<clone> (clone an object)
97              
98             =item * L<insert> (inserts object into database)
99              
100             =item * L<update> (updates database to match the object)
101              
102             =item * L<delete> (deletes record from database if nothing references it)
103              
104             =item * L<load_by_cid> (loads a single activity into an object)
105              
106             =item * L<load_by_path> (loads a single activity into an object)
107              
108             =back
109              
110             L<App::Dochazka::REST::Model::Component> also exports some convenience
111             functions:
112              
113             =over
114              
115             =item * L<cid_exists> (boolean function)
116              
117             =item * L<path_exists> (boolean function)
118              
119             =item * L<cid_by_path> (given a path, returns CID)
120              
121             =item * L<get_all_components> (self-explanatory)
122              
123             =back
124              
125             For basic C<component> object workflow, see the unit tests in
126             C<t/model/component.t>.
127              
128             =cut
129              
130             use Exporter qw( import );
131 41     41   71496 our @EXPORT_OK = qw( cid_exists path_exists cid_by_path get_all_components );
  41         111  
  41         42269  
132              
133              
134              
135              
136             =head1 METHODS
137              
138              
139             =head2 insert
140              
141             Instance method. Takes the object, as it is, and attempts to insert it into
142             the database. On success, overwrites object attributes with field values
143             actually inserted. Returns a status object.
144              
145             =cut
146              
147             my $self = shift;
148             my ( $context ) = validate_pos( @_, { type => HASHREF } );
149 0     0 1    
150 0           return $CELL->status_err( 'DOCHAZKA_MALFORMED_400' ) unless
151             (
152             $self->{'path'} and $self->{'source'} and $self->{'acl'} and
153             scalar(
154             grep { $self->{'acl'} eq $_ } ( 'admin', 'active', 'inactive', 'passerby' )
155             )
156 0 0 0       );
  0   0        
      0        
157              
158             my $status = cud(
159             conn => $context->{'dbix_conn'},
160             eid => $context->{'current'}->{'eid'},
161             object => $self,
162 0           sql => $site->SQL_COMPONENT_INSERT,
163             attrs => [ 'path', 'source', 'acl', 'validations' ],
164             );
165              
166             $self->create_file if $status->ok;
167              
168 0 0         return $status;
169             }
170 0            
171              
172             =head2 update
173              
174             Instance method. Assuming that the object has been prepared, i.e. the CID
175             corresponds to the component to be updated and the attributes have been
176             changed as desired, this function runs the actual UPDATE, hopefully
177             bringing the database into line with the object. Overwrites all the
178             object's attributes with the values actually written to the database.
179             Returns status object.
180              
181             =cut
182              
183             my $self = shift;
184             my ( $context ) = validate_pos( @_, { type => HASHREF } );
185              
186 0     0 1   return $CELL->status_err( 'DOCHAZKA_MALFORMED_400' ) unless
187 0           (
188             $self->{'cid'} and
189             (
190             $self->{'path'} or $self->{'source'} or $self->{'acl'}
191             )
192             );
193 0 0 0        
      0        
194             return $CELL->status_err( 'DOCHAZKA_MALFORMED_400' ) if
195             (
196             $self->{'acl'} and not scalar(
197             grep { $self->{'acl'} eq $_ } ( 'admin', 'active', 'inactive', 'passerby' )
198             )
199             );
200 0 0 0        
  0            
201             my $status = cud(
202             conn => $context->{'dbix_conn'},
203             eid => $context->{'current'}->{'eid'},
204             object => $self,
205             sql => $site->SQL_COMPONENT_UPDATE,
206 0           attrs => [ 'path', 'source', 'acl', 'validations', 'cid' ],
207             );
208              
209             $self->create_file if $status->ok;
210              
211             return $status;
212 0 0         }
213              
214 0            
215             =head2 delete
216              
217             Instance method. Assuming the CID really corresponds to the component to be
218             deleted, this method will execute the DELETE statement in the database. No
219             attempt is made to protect from possible deleterious consequences of
220             deleting components. Returns a status object.
221              
222             =cut
223              
224             my $self = shift;
225             my ( $context ) = validate_pos( @_, { type => HASHREF } );
226              
227             my $status = cud(
228 0     0 1   conn => $context->{'dbix_conn'},
229 0           eid => $context->{'current'}->{'eid'},
230             object => $self,
231             sql => $site->SQL_COMPONENT_DELETE,
232             attrs => [ 'cid' ],
233 0           );
234             if ( $status->ok ) {
235             $self->delete_file;
236             $self->reset( cid => $self->{cid} );
237             }
238 0 0          
239 0           return $status;
240 0           }
241              
242              
243 0           =head2 load_by_cid
244              
245             Loads component from database, by the CID provided in the argument list,
246             into a newly-spawned object. The CID must be an exact match. Returns a
247             status object: if the object is loaded, the status code will be
248             'DISPATCH_RECORDS_FOUND' and the object will be in the payload; if
249             the CID is not found in the database, the status code will be
250             'DISPATCH_NO_RECORDS_FOUND'. A non-OK status indicates a DBI error.
251              
252             =cut
253              
254             my $self = shift;
255             my ( $conn, $cid ) = validate_pos( @_,
256             { isa => 'DBIx::Connector' },
257             { type => SCALAR },
258             );
259 0     0 1    
260 0           return load(
261             conn => $conn,
262             class => __PACKAGE__,
263             sql => $site->SQL_COMPONENT_SELECT_BY_CID,
264             keys => [ $cid ],
265 0           );
266             }
267              
268              
269             =head2 load_by_path
270              
271             Analogous method to L<"load_by_cid">.
272              
273             =cut
274              
275             my $self = shift;
276             my ( $conn, $path ) = validate_pos( @_,
277             { isa => 'DBIx::Connector' },
278             { type => SCALAR },
279             );
280              
281 0     0 1   $path =~ s{^/}{};
282 0            
283             return load(
284             conn => $conn,
285             class => __PACKAGE__,
286             sql => $site->SQL_COMPONENT_SELECT_BY_PATH,
287 0           keys => [ $path ],
288             );
289 0           }
290              
291              
292             =head2 create_file
293              
294             Create Mason component file under $comp_root
295              
296             =cut
297              
298             my $self = shift;
299             my ( undef, $dirspec, $filespec ) = File::Spec->splitpath( $self->path );
300             my $full_path = File::Spec->catfile( $comp_root, $dirspec );
301             mkpath( $full_path, 0, 0750 );
302             $full_path = File::Spec->catfile( $full_path, $filespec );
303             open(my $fh, '>', $full_path) or die "Could not open file '$full_path' $!";
304             print $fh $self->source;
305 0     0 1   close $fh;
306 0           return;
307 0           }
308 0            
309 0            
310 0 0         =head2 delete_file
311 0            
312 0           Delete Mason component file under $comp_root
313 0            
314             =cut
315              
316             my $self = shift;
317             my $full_path = File::Spec->catfile( $comp_root, $self->path );
318             my $count = unlink $full_path;
319             if ( $count == 1 ) {
320             $log->info( "Component.pm->delete_file: deleted 1 file $full_path" );
321             } else {
322             $log->error( "Component.pm->delete_file: deleted $count files" );
323             }
324 0     0 1   return;
325 0           }
326 0            
327 0 0          
328 0           =head2 generate
329              
330 0           Generate output
331              
332 0           =cut
333              
334             my $self = shift;
335             my %ARGS = @_;
336             my $path = $self->path;
337              
338             # the path in the Component object may or may not start with a '/'
339             # Mason requires that it start with an '/', even though it's relative
340             $path = '/' . $path unless $path =~ m{^/};
341              
342             # the path should exist and be readable
343 0     0 1   my $full_path = File::Spec->catfile( $comp_root, $self->path );
344 0           return "$full_path does not exist" unless -e $full_path;
345 0           return "$full_path is not readable" unless -r $full_path;
346              
347             # only top-level components can be used to produce output
348             # top-level components must end in '.mc' or '.mp', but Mason
349 0 0         # expects the component name to be specified without the extension
350             return $self->path . " is not a top-level component" unless $path =~ m/\.m[cp]$/;
351             $path =~ s/\.m[cp]$//;
352 0            
353 0 0         return $interp->run($path, %ARGS)->output;
354 0 0         }
355              
356              
357             =head1 FUNCTIONS
358              
359 0 0         The following functions are not object methods.
360 0            
361              
362 0           =head2 cid_exists
363              
364             Boolean function
365              
366              
367             =head2 path_exists
368              
369             Boolean function
370              
371             =cut
372              
373             BEGIN {
374             no strict 'refs';
375             *{'cid_exists'} = App::Dochazka::REST::Model::Shared::make_test_exists( 'cid' );
376             *{'path_exists'} = App::Dochazka::REST::Model::Shared::make_test_exists( 'path' );
377             }
378              
379              
380             =head2 cid_by_path
381              
382             Given a path, attempt to retrieve the corresponding CID.
383 41     41   313 Returns CID or undef on failure.
  41         102  
  41         2618  
384 41     41   335  
  41         235  
385 41         208 =cut
  41         6968  
386              
387             my ( $conn, $path ) = validate_pos( @_,
388             { isa => 'DBIx::Connector' },
389             { type => SCALAR },
390             );
391              
392             my $status = __PACKAGE__->load_by_path( $conn, $path );
393             return $status->payload->{'cid'} if $status->code eq 'DISPATCH_RECORDS_FOUND';
394             return;
395             }
396              
397 0     0 1    
398              
399             =head2 get_all_components
400              
401             Returns a reference to a hash of hashes, where each hash is one component object.
402 0            
403 0 0         =cut
404 0            
405             my $conn = shift;
406            
407             my $sql = $site->SQL_COMPONENT_SELECT_ALL;
408              
409             return load_multiple(
410             conn => $conn,
411             class => __PACKAGE__,
412             sql => $sql,
413             keys => [],
414             );
415             }
416 0     0 1    
417              
418 0            
419              
420 0           =head1 AUTHOR
421              
422             Nathan Cutler, C<< <presnypreklad@gmail.com> >>
423              
424             =cut
425              
426             1;
427