File Coverage

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


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   1284 use 5.012;
  1         5  
36 1     1   9 use strict;
  1         12  
  1         42  
37 1     1   5 use warnings;
  1         2  
  1         31  
38              
39 1     1   335 use Params::Validate qw( :all );
  1         8107  
  1         178  
40 1     1   223 use Test::Deep::NoTest;
  1         6480  
  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   348 no strict 'refs';
  1         3  
  1         712  
161 8     8 1 26 my ( $module, @attrs ) = @_;
162 8         10 my $fn;
163              
164             # generate 'spawn' method
165 8         14 $fn = $module . "::spawn";
166 8         17 *{ $fn } = $make{"spawn"}->();
  8         40  
167              
168             # generate filter, reset, TO_JSON, compare, compare_disabled, clone, attrs, get and set
169             map {
170 8         16 $fn = $module . '::' . $_;
  72         153  
171 72         160 *{ $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         15 $fn = $module . '::' . $_;
  46         86  
177 46         77 *{ $fn } = $make{"accessor"}->( $_ );
  46         202  
178             } @attrs;
179              
180 8         172 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 30 }
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 19 my ( @attr ) = validate_pos( @_, map { { type => SCALAR }; } @_ );
  46         233  
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         53 }
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 16 my ( @attr ) = validate_pos( @_, map { { type => SCALAR }; } @_ );
  46         173  
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         95  
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 0     0 my %ARGS = validate( @_, $val_spec ) if @_ and defined $_[0];
252              
253             # Set attributes to run-time values sent in argument list.
254             # Attributes that are not in the argument list will get set to undef.
255 0         0 map { $self->{$_} = $ARGS{$_}; } @attr;
  0         0  
256              
257             # run the populate function, if any
258 0 0       0 $self->populate() if $self->can( 'populate' );
259              
260             # return an appropriate throw-away value
261 0         0 return;
262             }
263 8         31 }
264              
265              
266             =head2 make_accessor
267              
268             Returns a ready-made accessor.
269              
270             =cut
271              
272             sub make_accessor {
273 46     46 1 70 my ( $subname, $type ) = @_;
274 46   50     141 $type = $type || { type => SCALAR | UNDEF, optional => 1 };
275             sub {
276 0     0   0 my $self = shift;
277 0         0 validate_pos( @_, $type );
278 0 0       0 $self->{$subname} = shift if @_;
279 0 0       0 $self->{$subname} = undef unless exists $self->{$subname};
280 0         0 return $self->{$subname};
281 46         158 };
282             }
283              
284              
285             =head2 make_TO_JSON
286              
287             Returns a ready-made TO_JSON
288              
289             =cut
290              
291             sub make_TO_JSON {
292              
293 8     8 1 17 my ( @attr ) = validate_pos( @_, map { { type => SCALAR }; } @_ );
  46         190  
294              
295             return sub {
296 0     0   0 my $self = shift;
297 0         0 my $unblessed_copy;
298              
299 0         0 map { $unblessed_copy->{$_} = $self->{$_}; } @attr;
  0         0  
300              
301 0         0 return $unblessed_copy;
302             }
303 8         43 }
304              
305              
306             =head2 make_compare
307              
308             Returns a ready-made 'compare' method that can be used to determine if two objects are the same.
309              
310             =cut
311              
312             sub make_compare {
313              
314 8     8 1 18 my ( @attr ) = validate_pos( @_, map { { type => SCALAR }; } @_ );
  46         186  
315              
316             return sub {
317 0     0   0 my ( $self, $other ) = validate_pos( @_, 1, 1 );
318 0 0       0 return if ref( $other ) ne ref( $self );
319            
320 0         0 return eq_deeply( $self, $other );
321             }
322 8         46 }
323              
324              
325             =head2 make_compare_disabled
326              
327             Returns a ready-made 'compare' method that can be used to determine if two objects are the same.
328             For use with objects containing a 'disabled' property where 'undef' and 'false' are treatd
329             as functionally the same.
330              
331             =cut
332              
333             sub make_compare_disabled {
334              
335 8     8 1 18 my ( @attr ) = validate_pos( @_, map { { type => SCALAR }; } @_ );
  46         172  
336              
337             return sub {
338 0     0   0 my ( $self, $other ) = validate_pos( @_, 1, 1 );
339 0 0       0 return $self->compare( $other) unless grep { $_ eq 'disabled' } @attr;
  0         0  
340 0 0       0 return if ref( $other ) ne ref( $self );
341 0         0 my $self_disabled = $self->{'disabled'};
342 0         0 delete $self->{'disabled'};
343 0         0 my $other_disabled = $other->{'disabled'};
344 0         0 delete $other->{'disabled'};
345 0 0       0 return 0 unless eq_deeply( $self, $other );
346 0 0 0     0 return 0 unless ( ! $self_disabled and ! $other_disabled ) or ( $self_disabled and $other_disabled );
      0        
      0        
347 0         0 return 1;
348             }
349 8         41 }
350              
351              
352             =head2 make_clone
353              
354             Returns a ready-made 'clone' method.
355              
356             =cut
357              
358             sub make_clone {
359              
360 8     8 1 19 my ( @attr ) = validate_pos( @_, map { { type => SCALAR }; } @_ );
  46         177  
361              
362             return sub {
363 0     0   0 my ( $self ) = @_;
364              
365 0         0 my ( %h, $clone );
366 0         0 map { $h{$_} = $self->{$_}; } @attr;
  0         0  
367             {
368 1     1   8 no strict 'refs';
  1         7  
  1         254  
  0         0  
369 0         0 $clone = ( ref $self )->spawn( %h );
370             }
371              
372 0         0 return $clone;
373             }
374 8         56 }
375              
376              
377             =head2 make_attrs
378              
379             Returns a ready-made 'attrs' method.
380              
381             =cut
382              
383             sub make_attrs {
384              
385 8     8 1 17 my ( @attrs ) = validate_pos( @_, map { { type => SCALAR }; } @_ );
  46         186  
386              
387             return sub {
388 0     0   0 my ( $self ) = @_;
389              
390 0         0 return \@attrs;
391             }
392 8         41 }
393              
394              
395             =head2 make_get
396              
397             Returns a ready-made 'get' method.
398              
399             =cut
400              
401             sub make_get {
402              
403 8     8 1 13 my ( @attrs ) = validate_pos( @_, map { { type => SCALAR }; } @_ );
  46         168  
404              
405             return sub {
406 0     0   0 my ( $self, $attr ) = @_;
407              
408 0 0       0 if ( grep { $_ eq $attr } @attrs ) {
  0         0  
409 0         0 return $self->{$attr};
410             }
411             # unknown attribute
412 0         0 return;
413             }
414 8         43 }
415              
416              
417             =head2 make_set
418              
419             Returns a ready-made 'set' method, which takes the name of an attribute and a
420             value to set that attribute to. Returns true value on success, false on failure.
421              
422             =cut
423              
424             sub make_set {
425              
426 8     8 1 17 my ( @attrs ) = validate_pos( @_, map { { type => SCALAR }; } @_ );
  46         151  
427              
428             return sub {
429 0     0     my ( $self, $attr, $value ) = @_;
430              
431 0 0         if ( grep { $_ eq $attr } @attrs ) {
  0            
432 0           $self->{$attr} = $value;
433 0           return 1;
434             }
435             # unknown attribute
436 0           return 0;
437             }
438 8         37 }
439              
440             =head1 AUTHOR
441              
442             Nathan Cutler, C<< >>
443              
444             =cut
445              
446             1;
447