File Coverage

blib/lib/Games/Object/Manager.pm
Criterion Covered Total %
statement 289 312 92.6
branch 95 134 70.9
condition 17 33 51.5
subroutine 36 40 90.0
pod 0 18 0.0
total 437 537 81.3


line stmt bran cond sub pod time code
1             package Games::Object::Manager;
2              
3 9     9   40575 use strict;
  9         17  
  9         301  
4 9     9   46 use Exporter;
  9         18  
  9         527  
5              
6 9     9   47 use Carp qw(carp croak confess);
  9         14  
  9         511  
7 9     9   1959 use IO::File;
  9         31418  
  9         1724  
8 9     9   2128 use Games::Object::Common qw(FetchParams LoadData SaveData ANAME_MANAGER);
  9         19  
  9         715  
9              
10 9     9   64 use vars qw($VERSION @EXPORT_OK %EXPORT_TAGS @ISA);
  9         16  
  9         1103  
11              
12             $VERSION = "0.11";
13             @ISA = qw(Exporter);
14             @EXPORT_OK = qw($CompareFunction REL_NO_CIRCLE);
15             %EXPORT_TAGS = (
16             flags => [ qw(REL_NO_CIRCLE) ],
17             variables => [ qw($CompareFunction) ],
18             );
19              
20 9     9   49 use vars qw($CompareFunction);
  9         14  
  9         351  
21              
22             # Define flags.
23 9     9   46 use constant REL_NO_CIRCLE => 0x00000001; # Don't allow cir. relates
  9         16  
  9         2148  
24              
25             # Define the comparison function to use for processing order.
26             $CompareFunction = '_CompareDefault';
27              
28             # Define the default process info.
29             my @ProcessList = (
30             'process_queue',
31             'process_pmod',
32             'process_tend_to',
33             );
34             my $ProcessLimit = 100;
35              
36             ####
37             ## INTERNAL FUNCTIONS
38              
39             # Default comparison function when determining the order of processing of
40             # two objects.
41              
42 27     27   88 sub _CompareDefault { $b->priority() <=> $a->priority() }
43              
44             # Comparison function when using the creation order option
45              
46             sub _CompareAddOrder {
47 0     0   0 my $cmp = $b->priority() <=> $a->priority();
48 0 0       0 $cmp == 0 ? $a->order() <=> $b->order() : $cmp;
49             }
50              
51             # Create a relation methods
52              
53             sub _CreateRelators
54             {
55 15     15   75 my %args = @_;
56 15         34 my $realname = $args{name};
57 15         28 my $name = $args{relate_method};
58 15         27 my $uname = $args{unrelate_method};
59 15         25 my $rname = $args{related_method};
60 15         30 my $iname = $args{is_related_method};
61 15         35 my $lname = $args{related_list_method};
62              
63 9     9   54 no strict 'refs';
  9         14  
  9         43463  
64             *$name = sub {
65 27     27   1393 my $man = shift;
66 27 50       106 my $args = ( ref($_[$#_]) eq 'HASH' ? pop @_ : {} );
67 27         158 $man->relate(how => $realname,
68             self => $_[0],
69             object => $_[1],
70             other => $_[2],
71             args => $args);
72 15 100       222 } if (!defined(&$name));
73             *$uname = sub {
74 8     8   16 my $man = shift;
75 8 50       35 my $args = ( ref($_[$#_]) eq 'HASH' ? pop @_ : {} );
76 8         48 $man->unrelate(how => $realname,
77             object => $_[0],
78             other => $_[1],
79             args => $args);
80 15 100       8718 } if (!defined(&$uname));
81             *$rname = sub {
82 161     161   861 my $man = shift;
83 161         470 $man->related(how => $realname, object => $_[0]);
84 15 100       124 } if (!defined(&$rname));
85             *$iname = sub {
86 8     8   44 my $man = shift;
87 8         25 $man->is_related(how => $realname, self => $_[0], object => $_[1]);
88 15 100       131 } if (!defined(&$iname));
89             *$lname = sub {
90 4     4   10 my $man = shift;
91 4         18 $man->related_list(how => $realname, self => $_[0]);
92 15 100       183 } if (!defined(&$lname));
93             }
94              
95             ####
96             ## CONSTRUCTOR
97              
98             # Basic constructor
99              
100             sub new
101             {
102 8     8 0 1043 my $proto = shift;
103 8   33     78 my $class = ref($proto) || $proto;
104 8         22 my $man = {};
105 8         25 my %args = ();
106              
107             # Fetch parameters.
108 8         115 FetchParams(\@_, \%args, [
109             [ 'opt', 'base_id', 0, 'int' ],
110             [ 'opt', 'process_list', \@ProcessList, 'arrayref' ],
111             [ 'opt', 'process_limit', $ProcessLimit, 'int' ],
112             ] );
113 8         38 bless $man, $class;
114              
115             # Define storage for created objects. Note that this means that objects
116             # will be persistent. They can go out of scope and still exist, since
117             # each is identified by a unique ID.
118 8         95 $man->{index} = {};
119              
120             # Define tables that handle object relationships
121 8         23 $man->{relation_def} = {};
122 8         66 $man->{relate_to} = {};
123 8         22 $man->{relate_from} = {};
124              
125             # Define a counter for creating objects when the user wants us to
126             # assume that every new object is unique. The starting number can be
127             # changed with base_id() but only if no objects have been created yet.
128 8         30 $man->{next} = $args{base_id};
129              
130             # Define a counter that will be used to track the order in which objects
131             # are created. This is to support a new feature in v0.05
132 8         21 $man->{order} = 0;
133              
134             # And if we are doing this, we want to try and use space efficiently by
135             # reclaiming unused IDs. Thus we track the lowest available opening.
136             # [ NOT YET IMPLEMENTED ]
137 8         20 $man->{reclaim} = 1;
138 8         36 $man->{avail} = 0;
139              
140             # Track the highest priority object.
141 8         18 $man->{highest_pri} = 0;
142              
143             # Define a table that shows what order process() is supposed to do
144             # things.
145 8         19 $man->{process_list} = $args{process_list};
146              
147             # Define a limit to how many times the same item can be processed in a
148             # queue (see process_queue() for details)
149 8         32 $man->{process_limit} = $args{process_limit};
150              
151             # Set the default inherit_from relationship.
152 8         67 $man->define_relation(
153             name => 'inherit',
154             relate_method => 'inherit',
155             unrelate_method => 'disinherit',
156             related_method => 'inheriting_from',
157             related_list_method => 'has_inherting',
158             is_related_method => 'is_inheriting_from',
159             flags => REL_NO_CIRCLE,
160             );
161              
162             # Done.
163 8         33 $man;
164             }
165              
166             # Constructor for loading entire container from a file.
167              
168             sub load
169             {
170 1     1 0 3 my $proto = shift;
171 1   33     8 my $class = ref($proto) || $proto;
172 1         2 my $file = shift;
173 1         2 my $filename;
174              
175             # If we got a filename instead of a file object, open the file.
176 1 50       5 if (!ref($file)) {
177 1         2 $filename = $file;
178 1         10 $file = IO::File->new();
179 1 50       67 $file->open("<$filename") or
180             croak "Unable to open manager file '$filename'";
181             }
182              
183             # Initialize the object.
184 1         54 my $man;
185 1 50       5 if (ref($proto)) {
186             # This is a "load in place", meaning we're reloading to an
187             # existing object, so clear out the old stuff.
188 0         0 $man = $proto;
189 0         0 foreach my $key (keys %$man) { delete $man->{$key}; }
  0         0  
190             } else {
191             # Totally new object originating from the file.
192 1         4 $man = {};
193 1         4 bless $man, $class;
194             }
195              
196             # Check the header to make sure this is manager data.
197 1         43 my $line = <$file>; chomp $line;
  1         4  
198 1 50       6 croak "Did not find manager header data in file"
199             if ($line ne 'OBJ:__MANAGER__');
200 1         2 $line = <$file>; chomp $line;
  1         2  
201 1 50       7 croak "Second line of manager data header bad"
202             if ($line !~ /^CL:(.+)$/);
203 1         3 my $mclass = $1;
204              
205             # Load.
206 1         6 LoadData($file, $man);
207 1 50       19 $file->close() if defined($filename);
208              
209             # Restore manager attributes to all objects.
210 1         29 foreach my $obj (values %{$man->{index}}) { $obj->manager($man); }
  1         6  
  8         26  
211              
212             # Restore relators.
213 1         3 foreach my $rel (values %{$man->{relation_def}}) {
  1         6  
214 4         28 _CreateRelators(%$rel);
215             }
216              
217             # Done.
218 1         6 bless $man, $mclass;
219 1         10 $man;
220             }
221              
222             ####
223             ## MANAGER DATA METHODS
224              
225             # Save the manager and its contents to a file.
226              
227             sub save
228             {
229 1     1 0 2 my $man = shift;
230 1         2 my $file = shift;
231 1         2 my $filename;
232              
233             # If we got a filename instead of a file object, open the file.
234 1 50       5 if (!ref($file)) {
235 1         2 $filename = $file;
236 1         15 $file = IO::File->new();
237 1 50       55 $file->open(">$filename") or
238             croak "Unable to open manager file '$filename'";
239             }
240              
241             # Save header. This indicates that this is indeed manager object
242             # data and preserves the class.
243 1         174 print $file "OBJ:__MANAGER__\n" .
244             "CL:" . ref($man) . "\n";
245              
246             # Save data. See the comments on the save() routine in Games::Object
247             # for why we copy the ref to an ordinary hash first.
248 1         24 my %hash = %$man;
249 1         6 SaveData($file, \%hash);
250 1 50       17 $file->close() if (defined($filename));
251 1         117 1;
252             }
253              
254             # "Find" an object (i.e. look up its ID). If given something that is
255             # already an object, validates that the object is still valid. If the
256             # assertion flag is passed, an invalid object will result in a fatal error.
257              
258             sub find
259             {
260 412     412 0 1395 my ($man, $id, $assert) = @_;
261              
262 412 100       853 if (!defined($id)) {
263 26 50       45 if ($assert) {
264 0         0 confess "Assertion failed: ID is undefined";
265             } else {
266 26         163 return undef;
267             }
268             }
269 386 50 33     910 $id = $id->id() if (ref($id) && UNIVERSAL::isa($id, 'Games::Object'));
270 386 100       893 if (defined($man->{index}{$id})) {
    100          
271 381         1433 $man->{index}{$id};
272             } elsif ($assert) {
273             # Report with confess() so user can see where the assertion was made
274 1         135 confess "Assertion failed: '$id' is not a valid/managed object ID";
275             } else {
276 4         21 undef;
277             }
278             }
279              
280             # Return the number of objects in the manager.
281              
282             sub total_objects {
283 0     0 0 0 my $man = shift;
284 0         0 scalar keys %{$man->{index}};
  0         0  
285             }
286              
287             # Returns the ID of an object, with the side effect that it validates that
288             # this object is really a Games::Object derivative and is being managed by
289             # this manager. The user specifies either the ID or the object ref. If valid,
290             # the ID is always returned (thus it can be used to guarantee the return of
291             # an ID when you're not sure if you were passed an object or the ID).
292              
293             sub id
294             {
295 359     359 0 791 my ($man, $obj, $assert) = @_;
296              
297 359 100 66     1965 if (ref($obj) && UNIVERSAL::isa($obj, 'Games::Object')) {
    100          
    100          
298 325         960 my $id = $obj->id();
299 325 50       1203 defined($man->{index}{$id}) ? $id : undef;
300             } elsif (defined($man->{index}{$obj})) {
301 32         75 $obj;
302             } elsif ($assert) {
303             # Report with confess() so user can see where the assertion was made
304 1         216 confess "Assertion failed: '$obj' is not a valid/managed object";
305             } else {
306 1         4 undef;
307             }
308             }
309              
310             ####
311             ## OBJECT MANAGEMENT METHODS
312              
313             # Add a new object to the manager. The user may either specify an ID (which
314             # must not already exist), or allow it to take a predefined ID from the object
315             # (if defined), or pick one on its own (if previous two undefined)
316              
317             sub add
318             {
319 33     33 0 1605 my ($man, $obj, $id) = @_;
320              
321             # Pick new ID if needed.
322 33 100       180 $id = $obj->id() if (!defined($id));
323 33 100       227 $id = $man->{next}++ if (!defined($id));
324              
325             # Make sure it does not exist.
326 33 100       341 croak "Attempt to add duplicate object ID '$id'"
327             if (defined($man->{index}{$id}));
328              
329             # Add it. Do this before adding the manager link so we don't get
330             # a call back to us.
331 32         90 $man->{index}{$id} = $obj;
332 32         103 $obj->id($id);
333              
334             # Add the manager attribute
335 32         89 $obj->manager($man);
336              
337             # Done.
338 32         101 $id;
339             }
340              
341             # Similar to add, but allows an object to already exist under this ID, in
342             # which case the old on is removed. Returns the same values as add(). The
343             # ID to replace is always taken from the existing object. The ID parameter
344             # is applied to the new object (thus it must not already exist).
345              
346             sub replace
347             {
348 0     0 0 0 my ($man, $obj, $id) = @_;
349              
350             # Get rid of the old object. Don't worry if the object does not
351             # already exist.
352 0         0 $man->remove($id);
353              
354             # Add new one.
355 0         0 $man->add($obj, $id);
356             }
357              
358             # Remove an object. Returns the object if the object was found and removed,
359             # undef if not. The on_removed action is invoked on the object (but before
360             # the object is actually removed so it can still access the manager linkage).
361             # User may specify additional args to be passed to the action() call.
362              
363             sub remove
364             {
365 1     1 0 3 my $man = shift;
366 1         3 my $self = shift;
367              
368             # If the last arg is a hash, this is additional args to any callback
369             # that might get invoked.
370 1 50 33     7 my $aargs = ( @_ && ref($_[$#_]) eq 'HASH' ? pop @_ : {} );
371              
372             # Any remaining arg is other.
373 1 50       3 my $other = ( @_ ? shift : $self );
374              
375             # If object does not exist, no need to go any further.
376 1         5 my $id = $man->id($self);
377 1 50       6 return undef if (!defined($man->{index}{$id}));
378              
379             # Fetch the object and invoke action.
380 1         4 $self = $man->find($id);
381 1         5 $self->action(other => $other,
382             action => "object:remove",
383             args => $aargs);
384              
385             # Break relationships TO this object. These are all done with the
386             # force option. This means that no tests will be done for each
387             # unrelate(), but post-unrelate() actions WILL occur.
388 1         2 my @hows = ();
389 1 50       6 @hows = keys %{$man->{relate_from}{$id}}
  1         5  
390             if (defined($man->{relate_from}{$id}));
391 1         2 foreach my $how (@hows) {
392 2         3 my @fobjs = @{$man->{relate_from}{$id}{$how}};
  2         9  
393 2         5 foreach my $fobj (@fobjs) {
394 4         21 $man->unrelate(
395             how => $how,
396             object => $fobj,
397             other => $other,
398             force => 1,
399             args => { source => 'remove:to', %$aargs },
400             );
401             }
402             }
403              
404             # Break all relationships FROM this object to others.
405 1         3 @hows = ();
406 1 50       5 @hows = keys %{$man->{relate_to}{$id}}
  1         5  
407             if (defined($man->{relate_to}{$id}));
408 1         4 foreach my $how (@hows) {
409 1         2 my @objs = map { $man->find($_) } @{$man->{relate_from}{$id}{$how}};
  0         0  
  1         5  
410 1         5 foreach my $obj (@objs) {
411 0         0 $man->unrelate(
412             how => $how,
413             object => $obj,
414             other => $other,
415             force => 1,
416             args => { source => 'remove:from', %$aargs }
417             );
418             }
419             }
420              
421             # Delete from internal tables, which should remove all references to
422             # it save the one we have.
423 1         3 delete $man->{index}{$id};
424 1         3 delete $man->{relate_to}{$id};
425 1         5 delete $man->{relate_from}{$id};
426              
427             # Remove the manager attribute.
428 1         4 $self->manager(undef);
429              
430             # Done.
431 1         4 $self;
432             }
433              
434             # Go down the complete list of objects and perform a method call on each. If
435             # no args are given, 'process' is assumed. This will call them in order of
436             # priority.
437             #
438             # The caller may choose to filter the list by providing a CODE ref as the
439             # first argument. Only the objects for which the CODE ref returns true are
440             # considered (new in v0.10).
441              
442             sub process
443             {
444 13     13 0 1152 my $man = shift;
445              
446             # Note that we grab the actual objects and not the ids in the sort.
447             # This is more efficient, as each object is simply a reference (a
448             # scalar with a fixed size) as opposed to a string (a scalar with
449             # a variable size).
450 13         20 my $method = shift;
451 13 100       36 my $code = ( ref($method) eq 'CODE' ? $method : undef );
452 13 100       40 $method = shift if ($code);
453 13         24 my @args = @_;
454 13 100       33 $method = 'process' if (!defined($method));
455              
456             # Derive the object list.
457 3         50 my @objs = (
458             $code ?
459 3         10 grep { &$code($_, @args) }
460 1         7 grep { UNIVERSAL::can($_, $method) }
461 30         112 sort $CompareFunction values %{$man->{index}}
462             :
463 12         92 grep { UNIVERSAL::can($_, $method) }
464 13 100       31 sort $CompareFunction values %{$man->{index}}
465             );
466              
467             # Process.
468 13 100       47 unshift @args, $man->{process_list} if ($method eq 'process');
469 13         23 foreach my $obj (@objs) {
470 29 50       151 $obj->$method(@args) if (UNIVERSAL::can($obj, $method));
471             }
472              
473             # Return the number of objects processed.
474 13         59 scalar(@objs);
475             }
476              
477             # Set/fetch the process list for the process() function. Note that the user is
478             # not limited to the methods found here. The methods can be in the subclass
479             # if desired. Note that we have no way to validate the method names here,
480             # so we take it on good faith that they exist.
481              
482             sub process_list {
483 0     0 0 0 my $man = shift;
484 0 0       0 if (@_) { @{$man->{process_list}} = @_ } else { @{$man->{process_list}} }
  0         0  
  0         0  
  0         0  
  0         0  
485             }
486              
487             ####
488             ## OBJECT RELATIONSHIP METHODS
489              
490             # Check to see if a relationship is valid. If assertion flag present, this
491             # will bomb the program if the relationship is not present.
492              
493             sub has_relation
494             {
495 684     684 0 989 my ($man, $how, $assert) = @_;
496              
497 684 0       4266 defined($man->{relation_def}{$how}) ? 1 :
    50          
498             $assert ? croak "'$how' is an invalid relationship type"
499             : 0;
500             }
501              
502             # Define a new relationship. This allows objects to be related with the
503             # relate() method, or via a relator method created.
504              
505             sub define_relation
506             {
507 11     11 0 61 my $man = shift;
508 11         30 my %args = ();
509              
510             # Fetch parameters.
511 11         168 FetchParams(\@_, \%args, [
512             [ 'req', 'name', undef, 'string' ],
513             [ 'opt', 'relate_method', undef, 'string' ],
514             [ 'opt', 'unrelate_method', undef, 'string' ],
515             [ 'opt', 'related_method', undef, 'string' ],
516             [ 'opt', 'related_list_method', undef, 'string' ],
517             [ 'opt', 'is_related_method', undef, 'string' ],
518             [ 'opt', 'on_remove', undef, 'callback' ],
519             [ 'opt', 'flags', 0, 'int' ],
520             ], 1 );
521              
522             # Add it. Note that we allow redefinition at will.
523 11         70 my $rname = $args{name};
524 11 50       57 $args{relate_method} = $rname
525             if (!$args{relate_method});
526 11 50       67 $args{unrelate_method} = "un${rname}"
527             if (!$args{unrelate_method});
528 11 50       312 $args{related_method} = "${rname}_to"
529             if (!$args{related_method});
530 11 50       43 $args{related_list_method} = "${rname}_list"
531             if (!$args{related_list_method});
532 11 50       69 $args{is_related_method} = "is_${rname}"
533             if (!$args{is_related_method});
534 11         44 $man->{relation_def}{$rname} = \%args;
535              
536             # Create relator.
537 11         63 _CreateRelators(%args);
538              
539             # Done.
540 11         37 1;
541             }
542              
543             # Relate two objects.
544              
545             sub relate
546             {
547 32     32 0 51 my $man = shift;
548 32         67 my %args = ();
549              
550             # Fetch parameters. Self is the thing being related to, object is
551             # the thing being related to it.
552             FetchParams(\@_, \%args, [
553 32     32   405 [ 'req', 'how', undef, sub { $man->has_relation(shift); } ],
  32         88  
554             [ 'req', 'self', undef, 'any' ],
555             [ 'req', 'object', undef, 'any' ],
556             [ 'opt', 'other', undef, 'any' ],
557             [ 'opt', 'force', 0, 'boolean' ],
558             [ 'opt', 'args', {}, 'hashref' ],
559             ] );
560 32         215 my $how = $args{how};
561 32         72 my $self = $args{self};
562 32         51 my $object = $args{object};
563 32         49 my $other = $args{other};
564 32         51 my $force = $args{force};
565 32         52 my $aargs = $args{args};
566              
567             # If other is undefined, then we set it equal to self, meaning we assume
568             # that the receipient of the object itself instigated the action.
569 32 50       74 $other = $self if (!defined($other));
570              
571             # Do it. First fetch necesary parameters.
572 32         82 my $rel = $man->{relation_def}{$how};
573 32         73 my $doaction = "object:on_" . $rel->{relate_method};
574 32         78 my $tryaction = "object:try_" . $rel->{relate_method};
575 32         91 my $idself = $man->id($self); $self = $man->find($idself);
  32         142  
576 32         93 my $idobject = $man->id($object); $object = $man->find($idobject);
  32         77  
577              
578             # Perform check to see if relationship is allowed. We do this
579             # before anything else, including attempting to unrelate it from
580             # whatever it may be currently related to. This way the relate
581             # check code can see how it is related now in case that means
582             # anything, plus it prevents orphaned objects (which would happen
583             # if we first unrelate()d it and then failed the relate() check).
584 32   66     193 my $check =
585             $force
586             ||
587             $self->action(
588             action => $tryaction,
589             object => $object,
590             other => $other,
591             args => $aargs);
592 32 100       170 return 0 if (!$check);
593              
594             # Relation is allowed, so check to see if already related.
595 31 100       122 if (defined($man->{relate_to}{$idobject}{$how})) {
596              
597             # Already related in this fashion.
598 5 50       42 if ($man->{relate_to}{$idobject}{$how} eq $idself) {
    50          
599             # And to the same object, so do nothing (successfully).
600 0         0 return 1;
601             } elsif ($man->unrelate(
602             how => $how,
603             object => $object,
604             force => $force,
605             args => { source => 'relate', %$aargs } )) {
606             # The unrelate from the previous object succeeded, so
607             # invoke myself to try again.
608 5         21 return $man->relate(@_);
609             } else {
610             # The unrelate failed, so no-go.
611 0         0 return 0;
612             }
613              
614             }
615              
616             # Not currently related to anything in this way. The first
617             # thing we do is check the REL_NO_CIRCLE flag. If set,
618             # then we make a check to see if a circular reference would
619             # result from this. If so, then bomb, as this is assumed to
620             # be a logic error in the main program.
621 26 50       93 if ($rel->{flags} & REL_NO_CIRCLE) {
622              
623             # Check to make sure no circular relationship would result from
624             # this (i.e. self is already related to object in this manner).
625 26 100       95 croak "Relating $idobject to $idself in manner $how would " .
626             "create a circular relationship"
627             if ($man->is_related(
628             object => $self,
629             self => $object,
630             how => $how,
631             distant => 1));
632              
633             }
634              
635             # Do it.
636 25         80 $man->{relate_to}{$idobject}{$how} = $idself;
637 25 100       107 $man->{relate_from}{$idself}{$how} = []
638             if (!defined($man->{relate_from}{$idself}{$how}));
639 25         35 push @{$man->{relate_from}{$idself}{$how}}, $idobject;
  25         83  
640              
641             # Invoke post-relate actions.
642 25         99 $self->action(
643             object => $object,
644             other => $other,
645             action => $doaction,
646             args => $aargs,
647             );
648              
649             # Done.
650 25         228 1;
651              
652             }
653              
654             # Return the object to which this one is related (if any)
655              
656             sub related
657             {
658 161     161 0 220 my $man = shift;
659 161         244 my %args = ();
660              
661             # Fetch parameters.
662             FetchParams(\@_, \%args, [
663 161     161   1235 [ 'req', 'how', undef, sub { $man->has_relation(shift) } ],
  161         396  
664             [ 'req', 'object', undef, 'any' ],
665             ] );
666 161         734 my $how = $args{how};
667 161         256 my $object = $args{object};
668 161         364 my $id = $man->id($object); $object = $man->find($id);
  161         361  
669              
670 161 100 100     1436 defined($man->{relate_to}{$id}) && # @*!&$ autovivication
671             defined($man->{relate_to}{$id}{$how}) ?
672             $man->find($man->{relate_to}{$id}{$how}) : undef;
673             }
674              
675             # Return a list of items that are related to a paricular object in a certain
676             # way.
677              
678             sub related_list
679             {
680 4     4 0 8 my $man = shift;
681 4         9 my %args = ();
682              
683             # Fetch parameters.
684             FetchParams(\@_, \%args, [
685 4     4   46 [ 'req', 'how', undef, sub { $man->has_relation(shift) } ],
  4         11  
686             [ 'req', 'self', undef, 'any' ],
687             ] );
688 4         20 my $how = $args{how};
689 4         10 my $self = $args{self};
690              
691             # Return list of objects.
692 4         9 my $id = $man->id($self);
693 4         9 my @list = ();
694 4 50 33     32 @list = map { $man->find($_) } @{$man->{relate_from}{$id}{$how}}
  7         17  
  4         13  
695             if (defined($man->{relate_from}{$id})
696             && defined($man->{relate_from}{$id}{$how}));
697 4         26 @list;
698             }
699              
700             # Check to see if two objects are related. By default, this checks only if
701             # two objects are DIRECTLY related. However, specifying the "distant" flag
702             # will perform a recursive check to see if the relationship exists indirectly.
703              
704             sub is_related
705             {
706 46     46 0 68 my $man = shift;
707 46         80 my %args = ();
708              
709             # Fetch parameters.
710             FetchParams(\@_, \%args, [
711 46     46   437 [ 'req', 'how', undef, sub { $man->has_relation(shift); } ],
  46         113  
712             [ 'req', 'object', undef, 'any' ],
713             [ 'opt', 'self', undef, 'any' ],
714             [ 'opt', 'distant', 0, 'boolean' ],
715             ] );
716 46         237 my $how = $args{how};
717 46         118 my $idobject = $man->id($args{object});
718 46         112 my $idself = $man->id($args{self});
719 46         75 my $distant = $args{distant};
720 46 50 33     187 return 0 if (!defined($idobject) || !defined($idself));
721              
722             # If idobject is related to nothing then no relation.
723 46 100 66     354 return 0 if (!defined($man->{relate_to}{$idobject})
724             || !defined($man->{relate_to}{$idobject}{$how}));
725              
726             # If there is a direct relationships, success.
727 20 100       361 return 1 if ($man->{relate_to}{$idobject}{$how} eq $idself);
728              
729             # If user did not want a distant relationship, then fail.
730 14 100       39 return 0 if (!$distant);
731              
732             # Otherwise, check what idobject is related to and see if that is
733             # related to idself.
734 12         56 $man->is_related(
735             object => $man->{relate_to}{$idobject}{$how},
736             self => $idself,
737             how => $how,
738             distant => 1);
739             }
740              
741             # Unrelate an object.
742              
743             sub unrelate
744             {
745 17     17 0 27 my $man = shift;
746 17         34 my %args = ();
747              
748             # Fetch parameters.
749             FetchParams(\@_, \%args, [
750 17     17   186 [ 'req', 'how', undef, sub { $man->has_relation(shift) } ],
  17         51  
751             [ 'req', 'object', undef, 'any' ],
752             [ 'opt', 'other', undef, 'any' ],
753             [ 'opt', 'args', {}, 'hashref' ],
754             ] );
755 17         98 my $how = $args{how};
756 17         32 my $object = $args{object};
757 17         24 my $other = $args{other};
758 17         28 my $aargs = $args{args};
759 17         40 my $rel = $man->{relation_def}{$how};
760 17         47 my $doaction = "object:on_" . $rel->{unrelate_method};
761 17         31 my $tryaction = "object:try_" . $rel->{unrelate_method};
762              
763             # Set the source if not already defined.
764 17 100       56 $aargs->{source} = 'direct' if (!defined($aargs->{source}));
765              
766             # Get ID and check if related.
767 17         47 my $idobject = $man->id($object); $object = $man->find($idobject);
  17         46  
768 17 100 66     142 if (defined($man->{relate_to}{$idobject})
769             && defined($man->{relate_to}{$idobject}{$how})) {
770             # Yes it is, so check that object to see if we can unrelate.
771 12         26 my $idself = $man->{relate_to}{$idobject}{$how};
772 12         28 my $self = $man->find($idself);
773 12 100       30 $other = $self if (!defined($other));
774 12         67 my $check =
775             $self->action(
776             object => $object,
777             other => $other,
778             action => $tryaction,
779             args => { %$aargs },
780             );
781 12 50       37 if ($check) {
782             # Check succeeded, so unrelate them.
783 12         46 delete $man->{relate_to}{$idobject}{$how};
784 12         20 my @nlist = ();
785 12         19 foreach my $item (@{$man->{relate_from}{$idself}{$how}}) {
  12         40  
786 30 100       96 push @nlist, $item if ($item ne $idobject);
787             }
788 12         26 @{$man->{relate_from}{$idself}{$how}} = @nlist;
  12         42  
789             # Invoke post-unrelate actions.
790 12         52 $self->action(
791             object => $object,
792             other => $other,
793             action => $doaction,
794             args => $aargs,
795             );
796 12         82 1;
797             } else {
798 0         0 0;
799             }
800             } else {
801             # Not related to anything in this manner. Since the end result
802             # is the same as the original condition, we consider this to
803             # be success.
804 5         53 1;
805             }
806             }
807              
808             1;