File Coverage

blib/lib/Tree/Family/Person.pm
Criterion Covered Total %
statement 173 182 95.0
branch 86 110 78.1
condition 30 39 76.9
subroutine 30 32 93.7
pod 19 19 100.0
total 338 382 88.4


line stmt bran cond sub pod time code
1             package Tree::Family::Person;
2 13     13   10936 use strict;
  13         24  
  13         669  
3 13     13   70 use Data::Dumper;
  13         22  
  13         842  
4 13     13   72 use List::Util qw(min max);
  13         22  
  13         4857  
5 13     13   22949 use Sub::Installer;
  13         61371  
  13         50096  
6              
7             =head1 NAME
8              
9             Tree::Family::Person
10              
11             =head1 SYNOPSIS
12              
13             my $p = Tree::Family::Person->new(
14             first_name => 'Fred',
15             last_name => 'Flintstone',
16             birth_date => '1901-09-01',
17             death_date => undef,
18             gender => 'male',
19             birth_place => 'Bedrock');
20              
21             $p->spouse($wilma);
22              
23             $p->dad($papa);
24              
25             $p->mom($mama);
26              
27             for ($p->kids) {
28             print $_->first_name;
29             }
30            
31             $p->set(last_name => 'Smith');
32              
33             =cut
34              
35             our %globalHash; # a hash from IDs to hashes of info
36             our @Fields = qw(first_name middle_name last_name birth_date death_date birth_place gender id generation);
37             our @RelationFields = qw(spouse mom dad kids partners);
38             our $keyMethod = 'first_name'; # Change during testing (see below)
39              
40             =head2 new
41              
42             Create a new person
43              
44             =cut
45              
46             sub new {
47 762     762 1 319172 my ($class, %args) = @_;
48 6858         19783 my $new_hash = {
49 762         1873 map { ($_ => $args{$_}) } @Fields
50             };
51 762 50       2790 my $new_id = _new_id($new_hash) or die "couldn't make id for new object".Dumper($new_hash);
52 762         1304 my $id_copy = $new_id;
53 762         1982 $globalHash{$id_copy} = $new_hash;
54 762         2609 my $new_object = bless \$new_id, $class;
55 762         8814 $new_object->set(id => $id_copy);
56 762         2758 return $new_object;
57             }
58              
59             #
60             # _delete_self
61             #
62             # remove a person
63             #
64             sub _delete_self {
65 4     4   9 my $self = shift;
66 4         26 delete $globalHash{$$self};
67             }
68              
69             sub _new_id {
70 762 50   762   2395 return time.$$.( int ((rand 1) * 10000000)) if $keyMethod eq 'time_pid_rand';
71 762         1192 my $h = shift;
72 762   66     2501 my $val = ($h->{$keyMethod} || (int ((rand 1) * 10000).$$));
73 762         1468 $val =~ tr/a-zA-Z0-9//cd;
74 762         992 my $i = 1;
75 762         1030 my $base = $val;
76 762         2473 while (exists($globalHash{$val})) {
77 3         5 $val = $base.$i;
78 3         7 $i++;
79             }
80 762         2340 return $val;
81             }
82              
83             =head2 first_name,middle_name,last_name,birth_date,death_date,birth_place,gender,id,generation
84              
85             Accessors, mutators
86              
87             =cut
88              
89             for my $field (@Fields) {
90             __PACKAGE__->install_sub({
91             $field => sub {
92 29402     29402   64406 my $self = shift;
93 29402 100       79977 $self->set($field => $_[0]) if @_==1;
94 29402         62832 $self->get($field);
95             },
96             });
97             }
98              
99             =head2 full_name
100              
101             first + middle + last
102              
103             =cut
104              
105             sub full_name {
106 0     0 1 0 my $self = shift;
107 0         0 return join ' ', grep defined, ($self->first_name,$self->middle_name, $self->last_name);
108             }
109              
110             =head2 set
111              
112             Set an attribute for a person (same as using mutators above)
113              
114             $person->set(first_name => 'Joe');
115              
116             =cut
117              
118             sub set {
119 3715     3715 1 6439 my ($self,$key,$value) = @_;
120 3715         5172 local $@;
121 3715 50       85361 die "bad key $key" unless grep /^$key$/, @Fields, @RelationFields;
122 3715         11200 $globalHash{$$self}{$key} = $value;
123 3715 100       12313 delete $globalHash{$$self}{$key} if !defined($value);
124             }
125              
126             =head2 get
127              
128             Get an attribute
129              
130             Same as using accessors above.
131              
132             $person->get('first_name');
133              
134             =cut
135              
136             sub get {
137 104646     104646 1 169453 my ($self,$key) = @_;
138 104646 50       2016898 die "bad key $key" unless grep /^$key$/, @Fields, @RelationFields;
139 104646 50       216453 die "no key to get" unless $key;
140 104646         564791 $globalHash{$$self}{$key};
141             }
142              
143             =head2 spouse
144              
145             Get/set spouse
146              
147             $fred->spouse($wilma)
148              
149             =cut
150              
151             sub spouse {
152 14057     14057 1 33625 my ($self,$spouse) = @_;
153 14057 100       49706 return $self->get('spouse') unless @_==2;
154 367 100       1432 if ($spouse) {
155 362 50       996 die "spouse is not a person" unless ref($spouse) eq 'Tree::Family::Person';
156 362         888 $self->set('spouse' => $spouse);
157 362         847 $spouse->set('spouse' => $self);
158 362         753 return;
159             }
160 5 100       15 return unless $self->get('spouse');
161 3         13 $self->get("spouse")->set('spouse' => undef);
162 3         9 $self->set('spouse' => undef);
163             }
164              
165             =head2 dad
166              
167             Get/set dad
168              
169             $luke->dad($darth)
170              
171             =cut
172              
173             sub dad {
174 6729     6729 1 10668 my ($self,$dad) = @_;
175 6729 100       22447 return $self->get('dad') unless @_==2;
176 376 100       868 if ($dad) {
177 370 50       1046 die "dad is not a person" unless ref($dad) eq 'Tree::Family::Person';
178 370         746 $self->set(dad => $dad);
179 370         766 $dad->add_kid($self);
180 370         811 return;
181             }
182 6         19 my $old_dad = $self->get('dad');
183 6 100       27 return unless $old_dad;
184 2         9 $self->set('dad' => undef);
185 2         46 $old_dad->delete_kid($self);
186             }
187              
188             =head2 mom
189              
190             Get/set mom
191              
192             $pebbles->mom($wilma)
193              
194             =cut
195              
196             sub mom {
197 7781     7781 1 14483 my ($self,$mom) = @_;
198 7781 100       24560 return $self->get('mom') unless @_==2;
199 378 100       1002 if ($mom) {
200 373 50       973 die "mom is not a person" unless ref($mom) eq 'Tree::Family::Person';
201 373         811 $self->set(mom => $mom);
202 373         1052 $mom->add_kid($self);
203 373         1039 return;
204             }
205 5         13 my $old_mom = $self->get('mom');
206 5 100       21 return unless $old_mom;
207 1         3 $self->set('mom' => undef);
208 1         8 $old_mom->delete_kid($self);
209             }
210              
211             =head2 add_kid
212              
213             Add a kid to a person
214              
215             $carol->add_kid($jan);
216             $carol->add_kid($marsha);
217             $carol->add_kid($cindy);
218              
219             =cut
220              
221             sub add_kid {
222 743     743 1 1196 my ($self,$kid) = @_;
223 743 50       1572 die "not adding undef kid" unless defined $kid;
224 743 100       1461 return if grep { $_->id eq $kid->id } $self->kids;
  64         176  
225 739 100       2023 $self->set(kids => []) unless $self->get('kids');
226 739         1185 push @{ $self->get('kids') }, $kid;
  739         1678  
227             }
228              
229             =head2 delete_kid
230              
231             Remove a kid from a person
232              
233             $someone->remove_kid($annie)
234              
235             =cut
236              
237             sub delete_kid {
238 5     5 1 13 my ($self, $which) = @_;
239 5 50       18 die "no kids to delete" unless $self->get('kids');
240 5         9 $self->set(kids => [ grep $_ ne $which, @{ $self->get('kids') }]);
  5         12  
241 5 100 100     21 $which->mom(undef) if $self->gender && $self->gender eq 'f' && $which->mom;
      100        
242 5 100 100     16 $which->dad(undef) if $self->gender && $self->gender eq 'm' && $which->dad;
      100        
243             }
244              
245             =head2 kids
246              
247             Return an array of kids
248              
249             print $_->name for $mike->kids
250              
251             =cut
252              
253             sub kids {
254 3692 100   3692 1 4553 return @{ shift->get('kids') || [] };
  3692         7764  
255             }
256              
257             =head2 has_partner
258              
259             Did $a have any kids with $b?
260              
261             print $a->has_partner($b) ? 'you betcha' : 'nope'
262              
263             =cut
264              
265             sub has_partner {
266 120     120 1 165 my ($self,$who) = @_;
267 120         237 return grep { $_->id eq $who->id } $self->partners;
  75         183  
268             }
269              
270             #
271             # _add_partner, _delete_partner
272             #
273             sub _add_partner {
274 60     60   96 my ($self, $partner) = @_;
275 60 100       148 $self->set(partners => []) unless $self->get('partners');
276 60 100       162 push @{ $self->get('partners') }, $partner unless $self->has_partner($partner);
  32         72  
277 60 100       155 $partner->_add_partner($self) unless $partner->has_partner($self);
278             }
279              
280             sub _delete_partner {
281 0     0   0 my ($self, $which) = @_;
282 0 0       0 die "no partners to delete" unless $self->get('partners');
283 0         0 $self->set(partners => [ grep $_->id ne $which->id, @{ $self->get('partners') }]);
  0         0  
284 0 0       0 $which->_delete_partner($self) if $which->has_partner($self);
285             }
286              
287             =head2 partners
288              
289             Get people with whom a person had kids.
290              
291             =cut
292              
293             sub partners {
294             # _set_all_partners must have been called
295 2502 100   2502 1 2818 return @{ shift->get('partners') || [] };
  2502         5285  
296             }
297              
298             =head2 find
299              
300             Find a person based on their attributes
301              
302             $class->find(first_name => "Bugs", last_name => "Bunny" );
303              
304             =cut
305              
306             sub find {
307 61     61 1 164 my ($class, %args) = @_;
308 61         1012 my @list = values %globalHash;
309 61         268 while (my ($key,$value) = each %args) {
310 61 50       166 if (defined $value) {
311 61   66     11201 @list = grep defined($_->{$key}) && $_->{$key} eq $value, @list;
312             } else {
313 0         0 @list = grep !defined($_->{$key}), @list;
314             }
315             }
316 61 50       206 return unless @list;
317 61 100       1939 return wantarray ? map { bless \(my $i = $_->{id}), $class } @list :
  775         2482  
318             bless \(my $j = $list[0]->{id}), $class;
319             }
320              
321             # _clear_generations
322             # Remove all the generation attributes from the graph
323              
324             sub _clear_generations {
325 12     12   34 my $self = shift;
326 12         819 delete $_->{generation} for values %globalHash;
327             }
328              
329             #
330             # _set_all_generations
331             #
332             # Set all the generations recursively based on kids/parents
333             #
334              
335             sub _set_all_generations {
336             # Sets a generation tag in each person, starting with ourselves.
337 2192     2192   3555 my ($self, $value) = @_;
338 2192 100       4575 return if defined($self->get('generation'));
339 751 50       1757 Carp::confess("missing generation") unless defined($value);
340 751         1888 $self->set(generation => $value);
341 751 100       2424 $self->dad->_set_all_generations($value - 1) if $self->dad;
342 751 100       1922 $self->mom->_set_all_generations($value - 1) if $self->mom;
343 751         1853 $_->_set_all_generations($value + 1) for $self->kids;
344 751 100       2202 $self->spouse->_set_all_generations($value) if $self->spouse;
345             }
346              
347             =head2 all
348              
349             Get all people
350              
351             Tree::Family::Person->all
352              
353             =cut
354              
355             sub all {
356 16     16 1 27 my $class = shift;
357 16         94 return map { bless \(my $o = $_->{id}), $class } values %globalHash;
  773         2260  
358             }
359              
360             =head2 partners_and_spouse
361              
362             Get a list of all people with whom a person had kids, and their spouse (if
363             they have one)
364              
365             =cut
366              
367             sub partners_and_spouse {
368 829     829 1 1053 my $self = shift;
369 829 100       1957 return ($self->partners, ($self->spouse() ? $self->spouse() : ()));
370             }
371              
372             sub _clear_all_partners {
373 16     16   655 delete $_->{partners} for values %globalHash;
374             }
375              
376             sub _set_all_partners {
377 16     16   39 my $class = shift;
378             # A partner is someone you have had a kid with who is not your spouse.
379 16         85 for my $person ($class->all) {
380 773 100 100     1467 if ( $person->dad
  7   100     21  
      66        
      100        
381             && $person->mom
382             && (!$person->mom->spouse() || $person->mom->spouse() ne $person->dad)
383             && !grep { $_ eq $person->dad } $person->mom->partners
384             ) {
385 22         69 $person->mom->_add_partner($person->dad);
386 22         74 $person->dad->_add_partner($person->mom);
387             }
388             }
389             }
390              
391             =head2 min_generation, max_generation
392              
393             The min/max numeric generation
394              
395             =cut
396              
397             sub min_generation {
398 12     12 1 168 for (values %globalHash) {
399 756 50       1614 die "missing generation for $_\n".Dumper(\%globalHash) unless defined($_->{generation});
400             }
401 12         630 return min map $_->{generation}, values %globalHash;
402             }
403              
404             sub max_generation {
405 13     13 1 850 return max map $_->{generation}, values %globalHash;
406             }
407              
408             =head2 Freeze, Toast
409              
410             Used for storage by Data::Dumper.
411              
412             =cut
413              
414             sub Freeze {
415 773     773 1 1025 my $self = shift;
416             #warn "freezing $$self";
417 773         1137 my %i = map { $_ => $self->get($_) } @Fields;
  6957         14046  
418 773         2130 for (@RelationFields) {
419 3865 100       12853 next if /kids|partners/i;
420 2319 100       4807 next unless $self->$_;
421 1484 50       3581 $i{$_} = ( $self->$_->isa('REF') ? ${ $self->$_ }->{id} : ${ $self->$_ } );
  0         0  
  1484         3307  
422             }
423 773         1853 for ($self->kids) {
424 754 50       906 push @{ $i{kids} }, ( $_->isa('REF') ? $$_->{id} : $$_ );
  754         5603  
425             }
426 773         2036 for ($self->partners) {
427 32 50       54 push @{ $i{partners} },( $_->isa('REF') ? $$_->{id} : $$_ );
  32         251  
428             }
429 773         4692 return bless \%i, "Tree::Family::Person";
430             # return value is ignored; you can't replace the object.
431             }
432              
433             sub Toast {
434 119     119 1 173 my $self = shift;
435 119         188 my $class = ref $self;
436 119         2846 my $data = $self;
437 119         213 my %i = map { $_ => $data->{$_} } @Fields;
  1071         2698  
438 119         357 for (@RelationFields) {
439 595 100       2047 next if /kids|partners/i;
440 357 100       976 next unless $data->{$_};
441 174         325 my $tmp = $data->{$_};
442 174 50 33     914 $i{$_} = ref $tmp && $tmp->isa('REF') ? bless \(my $id = $tmp->{id}), $class : bless \$tmp, $class;
443             }
444 119 100       175 for (@{ $data->{kids} || [] } ) {
  119         570  
445 112 50 33     181 push @{ $i{kids} }, ref $_ && $_->isa('REF') ? bless \(my $c = $_), $class : bless \$_, $class;
  112         723  
446             }
447 119 100       209 for (@{ $data->{partners} || [] } ) {
  119         556  
448 32 50 33     40 push @{ $i{partners} }, ref $_ && $_->isa('REF') ? bless \(my $c = $_), $class : bless \$_, $class;
  32         245  
449             }
450 119         259 my $id = $data->{id};
451 119         245 $globalHash{$id} = \%i;
452 119         188 $self = \$id;
453 119         618 return bless $self, $class;
454             }
455              
456             #sub DESTROY {
457             # my $self = shift;
458             # warn "destroying $$self ";
459             #for (values %globalHash) {
460             # next unless $_ && ref($_) eq 'Tree::Family::Person';
461             # $_->set('spouse' => undef);
462             # $_->set('dad' => undef);
463             # $_->set('mom' => undef);
464             #}
465             # delete $globalHash{$$self} if exists($globalHash{$$self});
466             #}
467              
468             1;
469