File Coverage

blib/lib/App/Dochazka/Model.pm
Criterion Covered Total %
statement 112 119 94.1
branch 11 20 55.0
condition 5 14 35.7
subroutine 26 28 92.8
pod 11 11 100.0
total 165 192 85.9


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