File Coverage

blib/lib/App/Dochazka/Common/Model.pm
Criterion Covered Total %
statement 68 129 52.7
branch 0 24 0.0
condition 1 14 7.1
subroutine 19 30 63.3
pod 12 12 100.0
total 100 209 47.8


line stmt bran cond sub pod time code
1             # *************************************************************************
2             # Copyright (c) 2014-2015, 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::Common::Model;
34              
35 1     1   1090 use 5.012;
  1         3  
36 1     1   6 use strict;
  1         6  
  1         23  
37 1     1   5 use warnings;
  1         2  
  1         37  
38              
39 1     1   579 use Params::Validate qw( :all );
  1         8769  
  1         202  
40 1     1   421 use Test::Deep::NoTest;
  1         8726  
  1         6  
41              
42              
43              
44             =head1 NAME
45              
46             App::Dochazka::Common::Model - functions shared by several modules within
47             the data model
48              
49              
50              
51              
52             =head1 SYNOPSIS
53              
54             Shared data model functions. All three functions are designed to be
55             used together as follows:
56              
57             package My::Package;
58              
59             use Params::Validate qw( :all );
60              
61             BEGIN {
62             no strict 'refs';
63             *{"spawn"} = App::Dochazka::Common::Model::make_spawn;
64             *{"reset"} = App::Dochazka::Common::Model::make_reset(
65             'attr1', 'attr2',
66             );
67             *{"attr1"} = App::Dochazka::Common::Model::make_accessor( 'attr1' );
68             *{"attr2"} = App::Dochazka::Common::Model::make_accessor( 'attr2', { type => HASHREF } );
69             }
70              
71             What this does:
72              
73             =over
74              
75             =item * create a C class method in your class
76              
77             =item * create a C instance method in your class
78              
79             =item * create a C accessor method in your class (type defaults to SCALAR)
80              
81             =item * create a C accessor method in your class (type HASHREF)
82              
83             =back
84              
85              
86             =head1 PACKAGE VARIABLES
87              
88             Dispatch table used in 'boilerplate'.
89              
90             =cut
91              
92             my %make = (
93             spawn => \&make_spawn,
94             filter => \&make_filter,
95             reset => \&make_reset,
96             TO_JSON => \&make_TO_JSON,
97             compare => \&make_compare,
98             compare_disabled => \&make_compare_disabled,
99             clone => \&make_clone,
100             accessor => \&make_accessor,
101             attrs => \&make_attrs,
102             get => \&make_get,
103             set => \&make_set,
104             );
105              
106              
107             =head1 FUNCTIONS
108              
109              
110             =head2 boilerplate
111              
112             Run all the necessary commands to "install" the methods inside your
113             module. Call like this:
114              
115             use App::Dochazka::Common::Model;
116             use constant ATTRS => qw( ... );
117              
118             BEGIN {
119             App::Dochazka::Common::Model::boilerplate( __PACKAGE__, ATTRS );
120             }
121              
122             where the constant ATTRS contains the list of object properties.
123              
124             This routine requires some explanation. It's purpose is to generate
125             "boilerplate" code for the modules under C.
126             That includes the following methods:
127              
128             =over
129              
130             =item * C
131              
132             =item * C
133              
134             =item * C
135              
136             =item * C
137              
138             =item * C
139              
140             =item * C
141              
142             =item * C
143              
144             =item * C
145              
146             =item * C
147              
148             =item * C
149              
150             =back
151              
152             as well as basic accessors for that model/class.
153              
154             The C routine takes a module name and a list of attributes (object
155             property names), and returns nothing.
156              
157             =cut
158              
159             sub boilerplate {
160 1     1   341 no strict 'refs';
  1         2  
  1         1051  
161 8     8 1 29 my ( $module, @attrs ) = @_;
162 8         14 my $fn;
163              
164             # generate 'spawn' method
165 8         14 $fn = $module . "::spawn";
166 8         21 *{ $fn } = $make{"spawn"}->();
  8         41  
167              
168             # generate filter, reset, TO_JSON, compare, compare_disabled, clone, attrs, get and set
169             map {
170 8         20 $fn = $module . '::' . $_;
  72         159  
171 72         161 *{ $fn } = $make{$_}->( @attrs );
  72         388  
172             } qw( filter reset TO_JSON compare compare_disabled clone attrs get set );
173              
174             # generate accessors (one for each property)
175             map {
176 8         17 $fn = $module . '::' . $_;
  46         96  
177 46         86 *{ $fn } = $make{"accessor"}->( $_ );
  46         276  
178             } @attrs;
179              
180 8         247 return;
181             }
182              
183              
184              
185             =head2 make_spawn
186              
187             Returns a ready-made 'spawn' method for your class/package/module.
188              
189             =cut
190              
191             sub make_spawn {
192              
193             return sub {
194 0     0   0 my $self = bless {}, shift;
195 0         0 $self->reset( @_ );
196 0         0 return $self;
197             }
198              
199 8     8 1 29 }
200              
201              
202             =head2 make_filter
203              
204             Given a list of attributes, returns a ready-made 'filter' routine
205             which takes a PROPLIST and returns a new PROPLIST from which all bogus
206             properties have been removed.
207              
208             =cut
209              
210             sub make_filter {
211              
212             # take a list consisting of the names of attributes that the 'filter'
213             # routine will retain -- these must all be scalars
214 8     8 1 20 my ( @attr ) = validate_pos( @_, map { { type => SCALAR }; } @_ );
  46         244  
215              
216             return sub {
217 0 0   0   0 if ( @_ % 2 ) {
218 0         0 die "Odd number of parameters given to filter routine!";
219             }
220 0         0 my %ARGS = @_;
221 0         0 my %PROPLIST;
222 0         0 map { $PROPLIST{$_} = $ARGS{$_}; } @attr;
  0         0  
223 0         0 return %PROPLIST;
224             }
225 8         55 }
226              
227              
228             =head2 make_reset
229              
230             Given a list of attributes, returns a ready-made 'reset' method.
231              
232             =cut
233              
234             sub make_reset {
235              
236             # take a list consisting of the names of attributes that the 'reset'
237             # method will accept -- these must all be scalars
238 8     8 1 18 my ( @attr ) = validate_pos( @_, map { { type => SCALAR }; } @_ );
  46         192  
239              
240             # construct the validation specification for the 'reset' routine:
241             # 1. 'reset' will take named parameters _only_
242             # 2. only the values from @attr will be accepted as parameters
243             # 3. all parameters are optional (indicated by 0 value in $val_spec)
244 8         27 my $val_spec;
245 8         15 map { $val_spec->{$_} = 0; } @attr;
  46         103  
246            
247             return sub {
248             # process arguments
249 0     0   0 my $self = shift;
250             #confess "Not an instance method call" unless ref $self;
251 0         0 my %ARGS;
252 0 0 0     0 %ARGS = validate( @_, $val_spec ) if @_ and defined $_[0];
253              
254             # Set attributes to run-time values sent in argument list.
255             # Attributes that are not in the argument list will get set to undef.
256 0         0 map { $self->{$_} = $ARGS{$_}; } @attr;
  0         0  
257              
258             # run the populate function, if any
259 0 0       0 $self->populate() if $self->can( 'populate' );
260              
261             # return an appropriate throw-away value
262 0         0 return;
263             }
264 8         39 }
265              
266              
267             =head2 make_accessor
268              
269             Returns a ready-made accessor.
270              
271             =cut
272              
273             sub make_accessor {
274 46     46 1 81 my ( $subname, $type ) = @_;
275 46   50     167 $type = $type || { type => SCALAR | UNDEF, optional => 1 };
276             sub {
277 0     0   0 my $self = shift;
278 0         0 validate_pos( @_, $type );
279 0 0       0 $self->{$subname} = shift if @_;
280 0 0       0 $self->{$subname} = undef unless exists $self->{$subname};
281 0         0 return $self->{$subname};
282 46         190 };
283             }
284              
285              
286             =head2 make_TO_JSON
287              
288             Returns a ready-made TO_JSON
289              
290             =cut
291              
292             sub make_TO_JSON {
293              
294 8     8 1 18 my ( @attr ) = validate_pos( @_, map { { type => SCALAR }; } @_ );
  46         205  
295              
296             return sub {
297 0     0   0 my $self = shift;
298 0         0 my $unblessed_copy;
299              
300 0         0 map { $unblessed_copy->{$_} = $self->{$_}; } @attr;
  0         0  
301              
302 0         0 return $unblessed_copy;
303             }
304 8         46 }
305              
306              
307             =head2 make_compare
308              
309             Returns a ready-made 'compare' method that can be used to determine if two objects are the same.
310              
311             =cut
312              
313             sub make_compare {
314              
315 8     8 1 16 my ( @attr ) = validate_pos( @_, map { { type => SCALAR }; } @_ );
  46         177  
316              
317             return sub {
318 0     0   0 my ( $self, $other ) = validate_pos( @_, 1, 1 );
319 0 0       0 return if ref( $other ) ne ref( $self );
320            
321 0         0 return eq_deeply( $self, $other );
322             }
323 8         44 }
324              
325              
326             =head2 make_compare_disabled
327              
328             Returns a ready-made 'compare' method that can be used to determine if two objects are the same.
329             For use with objects containing a 'disabled' property where 'undef' and 'false' are treatd
330             as functionally the same.
331              
332             =cut
333              
334             sub make_compare_disabled {
335              
336 8     8 1 16 my ( @attr ) = validate_pos( @_, map { { type => SCALAR }; } @_ );
  46         179  
337              
338             return sub {
339 0     0   0 my ( $self, $other ) = validate_pos( @_, 1, 1 );
340 0 0       0 return $self->compare( $other) unless grep { $_ eq 'disabled' } @attr;
  0         0  
341 0 0       0 return if ref( $other ) ne ref( $self );
342 0         0 my $self_disabled = $self->{'disabled'};
343 0         0 delete $self->{'disabled'};
344 0         0 my $other_disabled = $other->{'disabled'};
345 0         0 delete $other->{'disabled'};
346 0 0       0 return 0 unless eq_deeply( $self, $other );
347 0 0 0     0 return 0 unless ( ! $self_disabled and ! $other_disabled ) or ( $self_disabled and $other_disabled );
      0        
      0        
348 0         0 return 1;
349             }
350 8         43 }
351              
352              
353             =head2 make_clone
354              
355             Returns a ready-made 'clone' method.
356              
357             =cut
358              
359             sub make_clone {
360              
361 8     8 1 17 my ( @attr ) = validate_pos( @_, map { { type => SCALAR }; } @_ );
  46         182  
362              
363             return sub {
364 0     0   0 my ( $self ) = @_;
365              
366 0         0 my ( %h, $clone );
367 0         0 map { $h{$_} = $self->{$_}; } @attr;
  0         0  
368             {
369 1     1   9 no strict 'refs';
  1         8  
  1         347  
  0         0  
370 0         0 $clone = ( ref $self )->spawn( %h );
371             }
372              
373 0         0 return $clone;
374             }
375 8         69 }
376              
377              
378             =head2 make_attrs
379              
380             Returns a ready-made 'attrs' method.
381              
382             =cut
383              
384             sub make_attrs {
385              
386 8     8 1 17 my ( @attrs ) = validate_pos( @_, map { { type => SCALAR }; } @_ );
  46         204  
387              
388             return sub {
389 0     0   0 my ( $self ) = @_;
390              
391 0         0 return \@attrs;
392             }
393 8         43 }
394              
395              
396             =head2 make_get
397              
398             Returns a ready-made 'get' method.
399              
400             =cut
401              
402             sub make_get {
403              
404 8     8 1 16 my ( @attrs ) = validate_pos( @_, map { { type => SCALAR }; } @_ );
  46         181  
405              
406             return sub {
407 0     0   0 my ( $self, $attr ) = @_;
408              
409 0 0       0 if ( grep { $_ eq $attr } @attrs ) {
  0         0  
410 0         0 return $self->{$attr};
411             }
412             # unknown attribute
413 0         0 return;
414             }
415 8         52 }
416              
417              
418             =head2 make_set
419              
420             Returns a ready-made 'set' method, which takes the name of an attribute and a
421             value to set that attribute to. Returns true value on success, false on failure.
422              
423             =cut
424              
425             sub make_set {
426              
427 8     8 1 16 my ( @attrs ) = validate_pos( @_, map { { type => SCALAR }; } @_ );
  46         207  
428              
429             return sub {
430 0     0     my ( $self, $attr, $value ) = @_;
431              
432 0 0         if ( grep { $_ eq $attr } @attrs ) {
  0            
433 0           $self->{$attr} = $value;
434 0           return 1;
435             }
436             # unknown attribute
437 0           return 0;
438             }
439 8         46 }
440              
441             =head1 AUTHOR
442              
443             Nathan Cutler, C<< >>
444              
445             =cut
446              
447             1;
448