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             package App::Dochazka::REST::Model::Component;
34              
35 41     41   1471 use 5.012;
  41         150  
36 41     41   195 use strict;
  41         78  
  41         749  
37 41     41   185 use warnings;
  41         75  
  41         1200  
38 41     41   206 use App::CELL qw( $CELL $log $meta $site );
  41         85  
  41         4072  
39 41     41   10280 use App::Dochazka::REST::Mason qw( $comp_root $interp );
  41         124  
  41         3968  
40 41     41   1004 use App::Dochazka::REST::Model::Shared qw( cud load load_multiple priv_by_eid );
  41         91  
  41         2123  
41 41     41   229 use DBI;
  41         87  
  41         1207  
42 41     41   195 use File::Path;
  41         89  
  41         1535  
43 41     41   205 use File::Spec;
  41         96  
  41         693  
44 41     41   205 use JSON;
  41         89  
  41         322  
45 41     41   4197 use Params::Validate qw{:all};
  41         93  
  41         4993  
46 41     41   254 use Try::Tiny;
  41         83  
  41         1870  
47              
48             # we get 'spawn', 'reset', and accessors from parent
49 41     41   231 use parent 'App::Dochazka::Common::Model::Component';
  41         82  
  41         333  
50              
51              
52              
53              
54             =head1 NAME
55              
56             App::Dochazka::REST::Model::Component - component class
57              
58              
59              
60              
61             =head1 SYNOPSIS
62              
63             use App::Dochazka::REST::Model::Component;
64              
65             ...
66              
67              
68             =head1 DATA MODEL
69              
70             =head2 Components in the database
71              
72              
73             CREATE TABLE components (
74             cid serial PRIMARY KEY,
75             path varchar(2048) UNIQUE NOT NULL,
76             source text NOT NULL,
77             acl varchar(16) NOT NULL,
78             validations textj
79             )
80              
81              
82              
83             =head2 Components in the Perl API
84              
85             =over
86              
87             =item * constructor (L<spawn>)
88              
89             =item * basic accessors (L<cid>, L<path>, L<source>, L<acl>, L<validations>)
90              
91             =item * L<reset> (recycles an existing object by setting it to desired state)
92              
93             =item * L<TO_JSON> (returns 'unblessed' version of an Activity object)
94              
95             =item * L<compare> (compare two objects)
96              
97             =item * L<clone> (clone an object)
98              
99             =item * L<insert> (inserts object into database)
100              
101             =item * L<update> (updates database to match the object)
102              
103             =item * L<delete> (deletes record from database if nothing references it)
104              
105             =item * L<load_by_cid> (loads a single activity into an object)
106              
107             =item * L<load_by_path> (loads a single activity into an object)
108              
109             =back
110              
111             L<App::Dochazka::REST::Model::Component> also exports some convenience
112             functions:
113              
114             =over
115              
116             =item * L<cid_exists> (boolean function)
117              
118             =item * L<path_exists> (boolean function)
119              
120             =item * L<cid_by_path> (given a path, returns CID)
121              
122             =item * L<get_all_components> (self-explanatory)
123              
124             =back
125              
126             For basic C<component> object workflow, see the unit tests in
127             C<t/model/component.t>.
128              
129             =cut
130              
131 41     41   59443 use Exporter qw( import );
  41         93  
  41         36172  
132             our @EXPORT_OK = qw( cid_exists path_exists cid_by_path get_all_components );
133              
134              
135              
136              
137             =head1 METHODS
138              
139              
140             =head2 insert
141              
142             Instance method. Takes the object, as it is, and attempts to insert it into
143             the database. On success, overwrites object attributes with field values
144             actually inserted. Returns a status object.
145              
146             =cut
147              
148             sub insert {
149 0     0 1   my $self = shift;
150 0           my ( $context ) = validate_pos( @_, { type => HASHREF } );
151              
152             return $CELL->status_err( 'DOCHAZKA_MALFORMED_400' ) unless
153             (
154             $self->{'path'} and $self->{'source'} and $self->{'acl'} and
155             scalar(
156 0 0 0       grep { $self->{'acl'} eq $_ } ( 'admin', 'active', 'inactive', 'passerby' )
  0   0        
      0        
157             )
158             );
159              
160             my $status = cud(
161             conn => $context->{'dbix_conn'},
162 0           eid => $context->{'current'}->{'eid'},
163             object => $self,
164             sql => $site->SQL_COMPONENT_INSERT,
165             attrs => [ 'path', 'source', 'acl', 'validations' ],
166             );
167              
168 0 0         $self->create_file if $status->ok;
169              
170 0           return $status;
171             }
172              
173              
174             =head2 update
175              
176             Instance method. Assuming that the object has been prepared, i.e. the CID
177             corresponds to the component to be updated and the attributes have been
178             changed as desired, this function runs the actual UPDATE, hopefully
179             bringing the database into line with the object. Overwrites all the
180             object's attributes with the values actually written to the database.
181             Returns status object.
182              
183             =cut
184              
185             sub update {
186 0     0 1   my $self = shift;
187 0           my ( $context ) = validate_pos( @_, { type => HASHREF } );
188              
189             return $CELL->status_err( 'DOCHAZKA_MALFORMED_400' ) unless
190             (
191             $self->{'cid'} and
192             (
193 0 0 0       $self->{'path'} or $self->{'source'} or $self->{'acl'}
      0        
194             )
195             );
196              
197             return $CELL->status_err( 'DOCHAZKA_MALFORMED_400' ) if
198             (
199             $self->{'acl'} and not scalar(
200 0 0 0       grep { $self->{'acl'} eq $_ } ( 'admin', 'active', 'inactive', 'passerby' )
  0            
201             )
202             );
203              
204             my $status = cud(
205             conn => $context->{'dbix_conn'},
206 0           eid => $context->{'current'}->{'eid'},
207             object => $self,
208             sql => $site->SQL_COMPONENT_UPDATE,
209             attrs => [ 'path', 'source', 'acl', 'validations', 'cid' ],
210             );
211              
212 0 0         $self->create_file if $status->ok;
213              
214 0           return $status;
215             }
216              
217              
218             =head2 delete
219              
220             Instance method. Assuming the CID really corresponds to the component to be
221             deleted, this method will execute the DELETE statement in the database. No
222             attempt is made to protect from possible deleterious consequences of
223             deleting components. Returns a status object.
224              
225             =cut
226              
227             sub delete {
228 0     0 1   my $self = shift;
229 0           my ( $context ) = validate_pos( @_, { type => HASHREF } );
230              
231             my $status = cud(
232             conn => $context->{'dbix_conn'},
233 0           eid => $context->{'current'}->{'eid'},
234             object => $self,
235             sql => $site->SQL_COMPONENT_DELETE,
236             attrs => [ 'cid' ],
237             );
238 0 0         if ( $status->ok ) {
239 0           $self->delete_file;
240 0           $self->reset( cid => $self->{cid} );
241             }
242              
243 0           return $status;
244             }
245              
246              
247             =head2 load_by_cid
248              
249             Loads component from database, by the CID provided in the argument list,
250             into a newly-spawned object. The CID must be an exact match. Returns a
251             status object: if the object is loaded, the status code will be
252             'DISPATCH_RECORDS_FOUND' and the object will be in the payload; if
253             the CID is not found in the database, the status code will be
254             'DISPATCH_NO_RECORDS_FOUND'. A non-OK status indicates a DBI error.
255              
256             =cut
257              
258             sub load_by_cid {
259 0     0 1   my $self = shift;
260 0           my ( $conn, $cid ) = validate_pos( @_,
261             { isa => 'DBIx::Connector' },
262             { type => SCALAR },
263             );
264              
265 0           return load(
266             conn => $conn,
267             class => __PACKAGE__,
268             sql => $site->SQL_COMPONENT_SELECT_BY_CID,
269             keys => [ $cid ],
270             );
271             }
272              
273              
274             =head2 load_by_path
275              
276             Analogous method to L<"load_by_cid">.
277              
278             =cut
279              
280             sub load_by_path {
281 0     0 1   my $self = shift;
282 0           my ( $conn, $path ) = validate_pos( @_,
283             { isa => 'DBIx::Connector' },
284             { type => SCALAR },
285             );
286              
287 0           $path =~ s{^/}{};
288              
289 0           return load(
290             conn => $conn,
291             class => __PACKAGE__,
292             sql => $site->SQL_COMPONENT_SELECT_BY_PATH,
293             keys => [ $path ],
294             );
295             }
296              
297              
298             =head2 create_file
299              
300             Create Mason component file under $comp_root
301              
302             =cut
303              
304             sub create_file {
305 0     0 1   my $self = shift;
306 0           my ( undef, $dirspec, $filespec ) = File::Spec->splitpath( $self->path );
307 0           my $full_path = File::Spec->catfile( $comp_root, $dirspec );
308 0           mkpath( $full_path, 0, 0750 );
309 0           $full_path = File::Spec->catfile( $full_path, $filespec );
310 0 0         open(my $fh, '>', $full_path) or die "Could not open file '$full_path' $!";
311 0           print $fh $self->source;
312 0           close $fh;
313 0           return;
314             }
315              
316              
317             =head2 delete_file
318              
319             Delete Mason component file under $comp_root
320              
321             =cut
322              
323             sub delete_file {
324 0     0 1   my $self = shift;
325 0           my $full_path = File::Spec->catfile( $comp_root, $self->path );
326 0           my $count = unlink $full_path;
327 0 0         if ( $count == 1 ) {
328 0           $log->info( "Component.pm->delete_file: deleted 1 file $full_path" );
329             } else {
330 0           $log->error( "Component.pm->delete_file: deleted $count files" );
331             }
332 0           return;
333             }
334              
335              
336             =head2 generate
337              
338             Generate output
339              
340             =cut
341              
342             sub generate {
343 0     0 1   my $self = shift;
344 0           my %ARGS = @_;
345 0           my $path = $self->path;
346              
347             # the path in the Component object may or may not start with a '/'
348             # Mason requires that it start with an '/', even though it's relative
349 0 0         $path = '/' . $path unless $path =~ m{^/};
350              
351             # the path should exist and be readable
352 0           my $full_path = File::Spec->catfile( $comp_root, $self->path );
353 0 0         return "$full_path does not exist" unless -e $full_path;
354 0 0         return "$full_path is not readable" unless -r $full_path;
355              
356             # only top-level components can be used to produce output
357             # top-level components must end in '.mc' or '.mp', but Mason
358             # expects the component name to be specified without the extension
359 0 0         return $self->path . " is not a top-level component" unless $path =~ m/\.m[cp]$/;
360 0           $path =~ s/\.m[cp]$//;
361              
362 0           return $interp->run($path, %ARGS)->output;
363             }
364              
365              
366             =head1 FUNCTIONS
367              
368             The following functions are not object methods.
369              
370              
371             =head2 cid_exists
372              
373             Boolean function
374              
375              
376             =head2 path_exists
377              
378             Boolean function
379              
380             =cut
381              
382             BEGIN {
383 41     41   306 no strict 'refs';
  41         88  
  41         2251  
384 41     41   260 *{'cid_exists'} = App::Dochazka::REST::Model::Shared::make_test_exists( 'cid' );
  41         205  
385 41         179 *{'path_exists'} = App::Dochazka::REST::Model::Shared::make_test_exists( 'path' );
  41         5658  
386             }
387              
388              
389             =head2 cid_by_path
390              
391             Given a path, attempt to retrieve the corresponding CID.
392             Returns CID or undef on failure.
393              
394             =cut
395              
396             sub cid_by_path {
397 0     0 1   my ( $conn, $path ) = validate_pos( @_,
398             { isa => 'DBIx::Connector' },
399             { type => SCALAR },
400             );
401              
402 0           my $status = __PACKAGE__->load_by_path( $conn, $path );
403 0 0         return $status->payload->{'cid'} if $status->code eq 'DISPATCH_RECORDS_FOUND';
404 0           return;
405             }
406              
407              
408              
409             =head2 get_all_components
410              
411             Returns a reference to a hash of hashes, where each hash is one component object.
412              
413             =cut
414              
415             sub get_all_components {
416 0     0 1   my $conn = shift;
417            
418 0           my $sql = $site->SQL_COMPONENT_SELECT_ALL;
419              
420 0           return load_multiple(
421             conn => $conn,
422             class => __PACKAGE__,
423             sql => $sql,
424             keys => [],
425             );
426             }
427              
428              
429              
430              
431             =head1 AUTHOR
432              
433             Nathan Cutler, C<< <presnypreklad@gmail.com> >>
434              
435             =cut
436              
437             1;
438